Android Wear Security Analysis

By Jahmel Harris on 22 May, 2015

Jahmel Harris

22 May, 2015

As the name suggests, Android Wear is a version of Android designed to be run on Wearables; devices which are designed to be worn and provide functionality by interacting with smartphones and tablets.

Wearables contain very little storage and processing power and are instead used to display notifications and messages sent from the main device. According to the marketing, Android Wear will allow its wearers to respond to important notifications and messages quicker than ever before. Motivated by the rise in popularity of wearable devices, MWR decided to perform a security review of Android Wear running on the LG G watch. The goal was to understand if introducing Android Wear to current Android applications introduces any additional security risk that developers and users should be aware of.

By studying the Android Wear documentation and writing some sample applications it became apparent that Android Wear works as follows:

Android Wear applications must implement the wearable listener interface, the life cycle of which is handled by the Google Play Service (as will be shown later in this article). By analysing applications built for a wearable device, the way Android Wear handles messages becomes clear.

Wearable applications must define a WearableListenerService class with the intent filter com.google.android.gms.wearable.BIND_LISTENER. Using an intent filter has the effect of exporting the service even though recent versions of Android do not export services by default. This allows another application to bind to an instance of the WearableListenerService. Interestingly, no permission is required to bind to services defined for Android Wear.

The goal of this research was to understand any security controls implemented in Android Wear and to determine the possibility of sending messages to arbitrary applications exporting the WearableListenerService.

First let’s assess the services running on the LG G watch.

WearableListenerService

Looking at the services running on the device, there are a few with wearable listeners. One example is com.google.android.music/com.google.android.music.MusicWearListenerService.

The first step is to generate an app which will try to bind to the MusicWearListenerService. For readability, only the relevant code is shown.

bind.java

BindToService() {
Intent intent = new Intent();
intent.setClassName("com.google.android.music",
"com.google.android.music.MusicWearListenerService");
this.bindService(intent, connection, Context.BIND_AUTO_CREATE);



private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder iservice) {
Log.d(TAG, "onServiceConnected: " + className.getClassName);
}
public void onServiceDisconnected(ComponentName className) {
Log.d(TAG, "onServiceDisconnected");
}
}

bindService is an asynchronous method, the second argument to which is an object containing methods which will be called when the service is available. Unfortunately, when executing the code above, onServiceConnected is never called. By decompiling MusicWearListenerService, it could be seen that it extends WearableDataListenerService. Similarly, decompiling WearableDataListenerService shows that it in turn extends WearableListenerService.

WearableListenerService.java contained a method onBind(), which is called when a request to bind to the service is made.

WearableListenerService.java

public final IBinder onBind(Intent paramIntent) {
if
("com.google.android.gms.wearable.BIND_LISTENER".equals(paramIntent.getAction()))
return this.Qv;
return null;
}

onBind will return an object if the action sent with the intent is equal to com.google.android.gms.wearable.BIND_LISTENER. Shown below is code with such an action added:

bind.java

BindToService() {
Intent intent = new Intent();
intent.setClassName("com.google.android.music", "com.google.android.music.MusicWearListenerService");
intent.setAction("com.google.android.gms.wearable.BIND_LISTENER");

this.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}

Viewing logcat on the wearable, the following message can be seen, indicating that it is indeed possible bind to a wearable service:

01-29 11:20:38.772 4311-4311/com.example.harrisj.servicebindwear D/SERVICETEST: onServiceConnected: com.google.android.music.MusicWearListenerService

What does the ability to bind to a wearable service allow an attacker to do? To determine this it is necessary to identify how an attacker could interact with the IBinder object received in onServiceConnected().

To interact with the IBinder object, it first needs to be cast to the correct type. Unfortunately for attackers, Google have obfuscated the binaries which contain the correct type, making identification more difficult. However, with a little inference it is possible to work out the required information. In the WearableListenerService class, the object returned in onBind(), named qV, is of type a and is instantiated in onCreate(). “a” is a private class which extends ae.a and contains four methods:

  • public void a(final ah paramah)
  • public void a(final ak paramak)
  • public void ab(final DataHolder paramDataHolder)
  • public void b(final ak paramak)

In order to aid in debugging, Google has included calls to Log.d in each of these methods. Although these methods are obfuscated, looking at the data that gets logged shows what appears to be pre-obfuscated method names, meaning that the above obfuscated names map to:

  • onMessageReceived
  • onPeerConnected
  • onDataItemChanged
  • onPeerDisconnected

These are methods of WearableListenerService1.

When interacting with services across applications, an AIDL file is usually used to generate the interface for the IBinder object received. Without this interface, it would not be possible to cast the IBinder object to the correct type, or know which methods are available. Unfortunately, the AIDL file is not available, so it is necessary to look at the decompiled code in more detail to see how to interact with this service.

As the received binder object extends ae, this is the class to decompile next. Although this class is obfuscated, it does follows the structure of an AIDL stub class. According to Google2:

“The Android SDK tools generate an interface in the Java programming language, based on your .aidl file. This interface has an inner abstract class named Stub that extends Binder and implements methods from your AIDL interface. You must extend the Stub class and implement the methods.”

An AIDL file is usually generated by the Android SDK tools to generate an interface for the object. This interface contains a class, called Stub, which has an onTransact method. onTransact takes several arguments, an integer representing which method to call, along with Parcel objects containing data. This allows data to be marshalled and unmarshalled between objects in different processes.

The interface generated from the AIDL file uses the following structure:

public abstract interface < interface name > extends Iinterface {
public abstract void < method description > throws RemoteException

public static abstract class Stub extends Binder implements < interface name > {
public static < interface name > asInterface(IBinder ibinder) {
//do stuff
}
public IBinder asBinder() {
return this;
}
public boolean onTransact(int paramInt1, Parcel, Parcel, int) {
//call methods based on paramInt1 using proxy object
}

private static class Proxy implements < interface name > {
private IBinder mRemote;
Proxy(IBinder) {
//set mRemote to IBinder parameter
}
public IBinder asBinder() {
return mRemote;
}
//methods belonging to interface, called from onTransact
}
}
}

This can be compared to the structure of ae.class

public interface ae extends android.os.IInterface {
void aa(com.google.android.gms.common.data.DataHolder dataHolder)
throws android.os.RemoteException;

void a(com.google.android.gms.wearable.internal.ai ai)
throws android.os.RemoteException;

void a(com.google.android.gms.wearable.internal.al al)
throws android.os.RemoteException;

void b(com.google.android.gms.wearable.internal.al al)
throws android.os.RemoteException;

static abstract class a extends android.os.Binder implements
com.google.android.gms.wearable.internal.ae {
public a() { /* compiled code */
}

public static com.google.android.gms.wearable.internal.ae bY(android.os.IBinder iBinder)
{ /* compiled code */
}

public android.os.IBinder asBinder()
{ /* compiled code */
}

public boolean onTransact(int code,
android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
/* compiled code */
}

private static class a implements
com.google.android.gms.wearable.internal.ae {
private android.os.IBinder le;

a(android.os.IBinder iBinder) { /* compiled code */
}

public android.os.IBinder asBinder() { /* compiled code */
}

public void aa(com.google.android.gms.common.data.DataHolder dataHolder)
throws android.os.RemoteException { /* compiled code */
}

public void a(com.google.android.gms.wearable.internal.ai ai)
throws android.os.RemoteException { /* compiled code */
}

public void a(com.google.android.gms.wearable.internal.al al)
throws android.os.RemoteException { /* compiled code */
}

public void b(com.google.android.gms.wearable.internal.al al)
throws android.os.RemoteException { /* compiled code */
}
}
}
}

From this it can be seen that, although names have been obfuscated, ae.a is in fact the interface generated by the AIDL file including the stub class which will handle our requests. With this in mind, the IBinder object received in onServiceConnected can be cast to ae, and methods subsequently called, as follows:

bind.java

public void onServiceConnected(ComponentName className, IBinder iservice) {
Log.d(TAG, "onServiceConnected: " + className.getClassName());

try {
/*in a non obfuscated class, this would correlate
to YourServiceInterface.Stub.asInterface(service)*/

ae my_ae = ae.a.bY(iservice);
my_ae.a((ai) null);
}

By calling method aa(DataHolder), it is possible to see that this relates to onDataItemChanged by matching the function definitions in ae to those of WearableListenerService. For the observant reader, it would appear that the methods defined in ae are named differently to those discovered before. Whilst this is true, the methods are in fact the same. It is thought to be likely that the version of the SDK used to create the MusicWearListenerService has been compiled and obfuscated differently to that used to create our test application. Ultimately, it is still possible to call the required methods.

After calling a method on the bound service, the following is found in the Android Wear logs:

01-29 13:07:04.175 4542-4542/com.example.harrisj.servicebindwear D/SERVICETEST: onServiceConnected: com.google.android.music.MusicWearListenerService
01-29 13:07:04.175 4542-4542/com.example.harrisj.servicebindwear D/SERVICETEST: iservice is not null
01-29 13:07:04.176 4542-4542/com.example.harrisj.servicebindwear D/SERVICETEST: calling my_ae.a
01-29 13:07:04.189 984-998/? W/Binder: Caught a RuntimeException from the binder stub implementation.
java.lang.SecurityException: Caller is not GooglePlayServices
at com.google.android.gms.wearable.WearableListenerService.pr(Unknown Source)
at com.google.android.gms.wearable.WearableListenerService.b(Unknown Source)
at com.google.android.gms.wearable.WearableListenerService$a.ab(Unknown Source)
at com.google.android.gms.wearable.internal.ae$a.onTransact(Unknown Source)
at android.os.Binder.execTransact(Binder.java:446)

This shows that although a service is exported with no permissions, there is a security check in place in WearableListenerService. WearableListenerService contains the following methods:

private void pr()
throws SecurityException {
int i = Binder.getCallingUid();
if (i == this.SX) return;
if ((GooglePlayServicesUtil.isPackageGoogleSigned(getPackageManager(),
"com.google.android.gms")) && (cU(i))) {
this.SX = i;
return;
}
throw new SecurityException("Caller is not GooglePlayServices");
}

private boolean cU(int paramInt) {
String[] arrayOfString = getPackageManager().getPackagesForUid(paramInt);
boolean bool = false;
if (arrayOfString != null);
for (int i = 0;; i++) {
int j = arrayOfString.length;
bool = false;
if (i < j) {
if ("com.google.android.gms".equals(arrayOfString[i])) bool = true;
} else return bool;
}
}

The method pr() first checks if com.google.android.gms is Google signed and then calls cU() to check if the calling process UID is for the package com.google.android.gms (the Google Play Service package). If the class is further decompiled, it can be seen that this security check happens in each method exposed in WearableListenerService.

The next obvious question is how Android Wear messages go from the Google Play Service to the applications.

WearableService

WearableService is a service with no permissions, exported from com.google.android.gms (i.e. the Google Play Service package). This can be discovered by either using Drozer, or by viewing the Android Manifest for com.google.android.gms.

dz> run app.service.info
Package: com.google.android.gms
com.google.android.gms.wearable.service.WearableService
Permission: null

The possibility of binding to the WearableService can now be investigated, and the controls which stop messages from passing the package boundary analysed.

The WearableService is responsible for sending and receiving data over Bluetooth and moving messages between the main applications and the wearable applications. This service can now be investigated to determine if it is possible to bind to it and identify any security controls which would stop an attacker from crafting messages destined for arbitrary applications.

After decompiling WearableService from the Google play package, the following code was found:

private List a() {
List localList = getPackageManager().queryIntentServices(new
Intent("com.google.android.gms.wearable.BIND_LISTENER"), 4);
ArrayList localArrayList = new ArrayList(localList.size());
Iterator localIterator = localList.iterator();
while (localIterator.hasNext()) {
ResolveInfo localResolveInfo = (ResolveInfo) localIterator.next();
try {
localArrayList.add(com.google.android.gms.wearable.node.b.a(this,
localResolveInfo.serviceInfo.packageName));
} catch (PackageManager.NameNotFoundException localNameNotFoundException) {}
}
if (Log.isLoggable("WearableService", 2)) Log.v("WearableService",
"getAllListenerServices: count=" + localArrayList.size());
return localArrayList;
}

This shows how Android Wear’s WearableService loads Wearable applications; it searches for services with an intent of com.google.android.gms.wearable.BIND_LISTENER and loads these packages into a list. It is assumed at this point, that the Google Play Service will then bind to the WearableListenerService service in each package. From the analysis of WearableListenerService it is clear that as WearableService belongs to a Google signed package, this will succeed and will be able to successfully communicate and call methods on this object.

As well as delivering messages to wearable applications, WearableService is also used to receive messages from wearable applications so they can be passed on to the relevant app. To understand how this works, the APIs used to communicate between the wearable app and its companion app can be analysed; these are MessageAPI, DataAPI and NodeAPI.

DataAPI is an API used to read and write data items and assets3. By decompiling and then searching a test application which uses this API, it is possible to find the code that implements the DataApi interface in com/google/android/gms/wearable/internal/f.java.

f.java

public PendingResult < DataApi.DataItemResult > putDataItem(GoogleApiClient paramGoogleApiClient,
final PutDataRequest paramPutDataRequest) {
return paramGoogleApiClient.a(new d(paramGoogleApiClient) {
protected void a(ba paramAnonymousba)
throws RemoteException {
paramAnonymousba.a(this, paramPutDataRequest);
}

public DataApi.DataItemResult aE(Status paramAnonymousStatus) {
return new f.b(paramAnonymousStatus, null);
}
});
}

Following the code flow, it is possible to find the Binder object (af.java) used to communicate with the IWearbleService class exported from com.google.android.gms. It is now clear how an application communicates with the WearableService, but is there anything that stops an attacker from performing the same actions manually?

As with WearableListenerService, no AIDL is made available. As before then, an application can be decompiled to identify an appropriate interface.

Once again, analysis starts with the WearableServiceonBind() method:

public IBinder onBind(Intent paramIntent) {
String str = paramIntent.getAction();
if ((Build.VERSION.SDK_INT >= 18) &&
("com.google.android.gms.wearable.BIND".equals(str))) return new o(this, this).asBinder();
return null;
}

By following the code flow, it is clear that when an application binds to the WearableService, it receives an object of type IGmsServiceBroker. IGmsServiceBroker has a method which takes an IGmsCallbacks class. A method in IGmscallbacks will eventually return a WearableService class.

At this stage it is finally possible to call WearableService as follows, specifying the package to which the should be delivered:

bind.java

my_jt.e(my_js,1,"com.google.android.music");

This generates a message using the data in my_js, destined for the package specified in the third argument. Logcat shows a security exception is thrown stating that the package specified must belong to the calling application.

W/AppOps ( 1077): Bad call: specified package com.google.android.music under UID 10203 but it is really 10082
E/AndroidRuntime(13094): FATAL EXCEPTION: main
E/AndroidRuntime(13094): Process: com.example.harrisj.servicetest, PID: 13094
E/AndroidRuntime(13094): java.lang.SecurityException: Unknown calling package name 'com.google.android.music'.

By further analysing the Google Play Service application, the following security check is performed in this function:

public static void c(Context paramContext, String paramString) {
int i = Binder.getCallingUid();
if (i == Process.myUid());
while (a(paramContext, i, paramString))
return;
throw new SecurityException(String.format("Unknown calling package name '%s'.", new Object[] {
paramString
}));
}

Note that there are often inaccuracies in decompiled code, hence this code may not be completely accurate.

In the call to a(paramContext, I, paramString), the UID from Binder.getCallingUid is used to get a list of all packages for that UID and compared to the string passed in as an argument. This means the security check will fail if the UID does not belong to the package name specified and a security exception will occur.

Conclusion

This research aimed to discover how Android Wear applications communicate and whether there are controls in place which stop non-privileged malware from delivering messages to arbitrary applications. The two methods explored (binding to WearableListenerService, and WearableService) showed that Google have taken the time to perform adequate security checks and unless a flaw is found in these checks, developing for Android Wear should not add a significant risk to your application from low privileged malware.