Hqdvista 2014/09/09 every

0 x01 background


Soot author Eric Bodden’s lab, Secure Software Engineering recently announced that they will present at SPSM’14 an Android vulnerability called denial-of-app-attack that affected models prior to 4.4.3. The POC and the corresponding Google COMMIT ID are given.

This is the corresponding links on googlecode code.google.com/p/android/i…

POC:github.com/secure-soft…

This problem can cause an attacker to specify that an application cannot be installed on a phone unless it has root permissions or factory Reset phones. Can be used by Trojan placeholder to refuse the installation of anti-virus software, or to refuse the installation of competing products. Here’s a breakdown of the vulnerability, according to Commit Diff and POC.

0x02 Symptom:


Create a malformed APK and install it. Since the dex of the MALformed APK is illegal, create a packagename for the APK. INSTALL_FAILED_DEXOPT is reported during installation and installation fails. However, if INSTALL_FAILED_UID_CHANGED is reported when installing real com.taobao. Taobao, even if PM install -r is specified, the subsequent installation fails. Taobao. Taobao, however, cannot be found among the installed applications on the occupied mobile phone. Naturally, the ghost of placeholder cannot be removed, so that real Taobao applications cannot be installed at all, and can be widely used in anti-virus software such as 360.

Nature of the problem:

Google’s Diff describes the problem as follows:

We’d otherwise leave the data dirs & native libraries lying around. This will leave the app permanently broken because the next install of the app will fail with INSTALL_FAILED_UID_CHANGED.

Also remove an unnecessary instance variable.

Cherry-pick from master Bug 13416059

At the end of the first installation (called “placeholder”), com.taobao. Taobao directory was already in /data/data/ and assigned a UID, such as U70 (10070), but at the second installation, PackageManager error UID_CHANGED, and did not reuse U70, this is why?

INSTALL_FAILED_DEXOPT and UID_CHANGED are in the following code blocks:

#!java
3622    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
3623            int parseFlags, int scanMode, long currentTime, UserHandle user) {
//....
4141        if ((scanMode&SCAN_NO_DEX) == 0) {
4142            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
4143                    == DEX_OPT_FAILED) {
4144                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
4145                return null;
4146            }
4147        }
Copy the code

The process of the scanPackageLI function is as follows:

#! /**/ / check whether the Package is repeated, INSTALL_FAILED_DUPLICATE_PACKAGE // Initialize Package source and resource directories 3686 File destCodeFile = new File(pkg.applicationInfo.sourceDir); 3687 File destResourceFile = new File(pkg.applicationInfo.publicSourceDir); / /... // Just create the setting, don't add it yet. For already existing packages 3812 // the PkgSetting exists already and doesn't have to be created. 3813 pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, 3814 destResourceFile, pkg.applicationInfo.nativeLibraryDir, 3815 pkg.applicationInfo.flags, user, false); Final Long scanFileTime = scanfile.lastModified (); final long scanFileTime = scanfile.lastModified (); 3926 final boolean forceDex = (scanMode&SCAN_FORCE_DEX) ! = 0; 3927 pkg.applicationInfo.processName = fixProcessName( 3928 pkg.applicationInfo.packageName, 3929 pkg.applicationInfo.processName, 3930 pkg.applicationInfo.uid); 3931 3932 File dataPath; 3933 if (mPlatformPackage == pkg) { //omit 3937 } else { 3938 // This is a normal package, need to make its data directory. 3939 dataPath = getDataPathForPackage(pkg.packageName, 0); 3940 3941 boolean uidError = false; 3942 3943 if (dataPath.exists()) { 3944 int currentUid = 0; 3945 try { 3946 StructStat stat = Libcore.os.stat(dataPath.getPath()); 3947 currentUid = stat.st_uid; 3948 } catch (ErrnoException e) { 3949 Slog.e(TAG, "Couldn't stat path " + dataPath.getPath(), e); 3950 } 3951 3952 // If we have mismatched owners for the data path, we have a problem. 3953 if (currentUid ! = pkg.applicationInfo.uid) { 3954 boolean recovered = false; 3955 if (currentUid == 0) { 3956 //omit... 3969 } 3970 if (! recovered && ((parseFlags&PackageParser.PARSE_IS_SYSTEM) ! = 0 3971 || (scanMode&SCAN_BOOTING) ! = 0)) { 3972 // If this is a system app, we can at least delete its 3973 // current data so the application will still work. 3974 //omit... 4001 } else if (! recovered) { 4002 // If we allow this install to proceed, we will be broken. 4003 // Abort, abort! 4004 mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED; 4005 return null; 4029 if (DEBUG_PACKAGE_SCANNING) {4030 if ((parseFlags & packageParser.parse_chatty)! = 0) 4031 Log.v(TAG, "Want this data dir: " + dataPath); 4032 } 4033 //invoke installer to do the actual installation 4034 int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid); 4035 if (ret < 0) {4036 // Error from Installer 4037 mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 4038 return null; 4039 } 4040 4041 if (dataPath.exists()) { 4042 pkg.applicationInfo.dataDir = dataPath.getPath(); 4043 } else { 4044 Slog.w(TAG, "Unable to create data directory: " + dataPath); 4045 pkg.applicationInfo.dataDir = null; 4046 } 4047 } //omit... //omit omit... If ((scanMode&SCAN_NO_DEX) == 0) {4142 if (performDexOptLI(PKG, forceDex, scanMode&SCAN_DEFER_DEX)! = 0) 4143 == DEX_OPT_FAILED) { 4144 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; 4145 return null; 4147 4146}}Copy the code

Then the principle of the vulnerability is clear. During the first placeholder installation, PMS deliberately made an error when it went to dexopt after the uid had been assigned to the data directory and written to /data/, resulting in the abnormal termination of the installation. At this time, the data directory that had been placed was not removed. During the second installation, the package was assigned a new UID, but a data directory with the same name but different UID already existed, resulting in the uID_changed error and the installation failed.

Why is a different UID assigned the second time you install it? Is the key to mSettings getPackageLPw, travelling to ref/frameworks/base/services/Java/com/android/server/PM/Settings. The Java

#! java private PackageSetting getPackageLPw(String name, PackageSetting origPackage, 359 String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, 360 String nativeLibraryPathString, int vc, int pkgFlags, 361 UserHandle installUser, boolean add, boolean allowInstall) { //omit... } else { 423 p = new PackageSetting(name, realName, codePath, resourcePath, 424 nativeLibraryPathString, vc, pkgFlags); 425 p.setTimeStamp(codePath.lastModified()); 426 p.sharedUser = sharedUser; 427 // If this is not a system app, it starts out stopped. 428 if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { 429 if (DEBUG_STOPPED) { 430 RuntimeException e = new RuntimeException("here"); 431 e.fillInStackTrace(); 432 Slog.i(PackageManagerService.TAG, "Stopping package " + name, e); 433 } 434 List<UserInfo> users = getAllUsers(); 435 if (users ! = null && allowInstall) { 436 for (UserInfo user : users) { 437 // By default we consider this app to be installed 438 // for the user if no user has been specified (which  439 // means to leave it at its original value, and the 440 // original default value is true), or we are being 441 // asked to install for all users, or this is the 442 // user we are installing for. 443 final boolean installed = installUser == null 444 || installUser.getIdentifier() == UserHandle.USER_ALL 445 || installUser.getIdentifier() == user.id; 446 p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT, 447 installed, 448 true, // stopped, 449 true, // notLaunched 450 null, null); 451 writePackageRestrictionsLPr(user.id); 452 } 453 } 454 } 455 if (sharedUser ! = null) { 456 p.appId = sharedUser.userId; 457 } else { 458 // Clone the setting here for disabled system packages 459 PackageSetting dis = mDisabledSysPackages.get(name); 460 if (dis ! = null) { //omit.. 484 } else { 485 // Assign new user id 486 p.appId = newUserIdLPw(p); // Key points 487} 488}Copy the code

Continue looking at newUserIdLPw

#! java private int newUserIdLPw(Object obj) { 2360 // Let's be stupidly inefficient for now... 2361 final int N = mUserIds.size(); 2362 for (int i = 0; i < N; I ++) {2363 if (muserids.get (I) == null) {2364 muserids.set (I, obj); 2365 return Process.FIRST_APPLICATION_UID + i; 2366 } 2367 } 2368 2369 // None left? 2370 if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) { 2371 return -1; 2372 } 2373 2374 mUserIds.add(obj); 2375 return Process.FIRST_APPLICATION_UID + N; 2376}Copy the code

MUserIds are an array of packaged esettings that maintains the current userID and is traversed for assignment at installation time. In the first malicious placeholder installation, mUserIds, an array of structures, had been added to package esettings, forming a sort of

#! java [PackageSetting{(10001, bla)}, ..., PackageSetting{(10070, com.taobao.taobao)}]Copy the code

But the last item was not removed when Dexopt failed. On subsequent installation, newUserIdLPw will iterate over the mUserIds, find no empty space, add a new one at the end, and form

#! java [PackageSetting{(10001, bla)},...,PackageSetting{(10070, com.taobao.taobao)},PackageSetting{(10071, com.taobao.taobao)}]Copy the code

The UID assigned to the two installations is different, triggering INSTALL_FAILED_UID_CHANGED.

But it’s worth noting that mUserIds are not solidified in packs.xml and packs.list at this point.

0x04 Further thinking


Wouldn’t killing system_server (soft reboot) and having it rescan and build up the mUserIds array fix this?

In theory, this is possible if no other applications have been installed before the reboot. The uid array is [(10001, BLA),…,()10069, haha)], so com.taobao.

But if after the restart and installed the other applications, then it will account for 10070 position, lead to taobao install again after 10071 and the uid should get back to belong to it/data/data/com taoba. Taobao… what a pity.

The above has been verified on stock ROM (Genymotion, SDK), Mi 2, Nexus, etc.

So it seems that the original author’s statement that only root or reset can fix this problem is not accurate, at least according to the poC and Google diff given in the experiment results that in some cases restart can fix this problem. What other details are waiting for SPSM’s paper. Overall, this is a fun trick bug, and from Issuelink’s point of view, there should be other types of exploits with the same effect.

0 x05 repair


Google fixes this:

In Google diff, the SCAN_DELETE_DATA_ON_FAILURES flag was added to delete the files left over when the installation failed.

#!java
@@ -4644,6 +4643,10 @@
         if ((scanMode&SCAN_NO_DEX) == 0) {
             if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
+                if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                    removeDataDirsLI(pkg.packageName);
+                }
+
                 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                 return null;
             }
@@ -4721,6 +4724,10 @@
                     PackageParser.Package clientPkg = clientLibPkgs.get(i);
                     if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                             == DEX_OPT_FAILED) {
+                        if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                            removeDataDirsLI(pkg.packageName);
+                        }
+
                         mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                         return null;
                     }
Copy the code

How to fix a placeholder attack:

Delete the data directory from root. I’ll have to reset it.