<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/2976

            0x01 背景


            Soot作者Eric Bodden所在的實驗室, Secure Software Engineering最近宣布他們將在SPSM'14上講述名為Denial-of-App-Attack的Android系統漏洞,影響4.4.3之前的機型,并給出了poc和對應的google commit id.

            這個在googlecode上對應的鏈接是 https://code.google.com/p/android/issues/detail?id=65790

            POC:https://github.com/secure-software-engineering/denial-of-app-attack

            該問題可以導致攻擊者可以指定應用使其無法安裝在手機上,除非有root權限或者factory reset手機。可以被木馬用來占位拒絕殺毒軟件的安裝,或者占位拒絕競品安裝。下面是根據commit diff和poc給出的漏洞具體分析。

            0x02 問題現象:


            下載安裝這個POC,可以看到其實就是指定一個packagename,例如com.taobao.taobao,然后生成了一個malformed的APK并執行安裝,由于該APK的dex是非法的,安裝的時候會報告INSTALL_FAILED_DEXOPT并安裝失敗。但如果隨后安裝真正的com.taobao.taobao時,即使指定了重新安裝選項(pm install -r),卻會報INSTALL_FAILED_UID_CHANGED,導致后續安裝失敗,而在被占位的手機上已安裝應用中卻找不到com.taobao.taobao,自然也無法清除掉占位的幽靈,造成真正的淘寶應用完全無法安裝,推而廣之可以用在360等殺毒軟件上。

            POC

            安裝之后

            正常應用無法安裝

            install -r

            0x03 問題本質:

            Google的diff對此問題的描述是:

            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

            通過觀察可以發現,第一次安裝(所謂“占位”)結束的時候,在/data/data/目錄下已經有了com.taobao.taobao目錄并分配了一個uid,例如u70(10070),但第二次安裝的時候,PackageManager卻出現了UID_CHANGED的error,而沒有復用u70,這是為什么?

            uid

            INSTALL_FAILED_DEXOPT和UID_CHANGED是在如下代碼塊中:

            #!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        }
            

            scanPackageLI函數流程大概如下:

            #!java
            /**/
            //檢查是否系統應用
            /**/
            //檢查Package是否重復,否則拋出PackageManager.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);
            //在這之后uid已經被指定了
            /**/
            //檢查簽名
            //檢查Provider權限
            
            //開始創建目錄
               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;
            4006                    }
                             } else {//目錄不存在,新建立
            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...
            //拷貝nativeLibrary
            //omit...
            //進行DexOpt
            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        }
            

            那么漏洞的原理就很清楚了,第一次占位安裝時,故意讓PMS在數據目錄已分配uid并寫入了/data/data/下之后走到dexopt時使其報錯,導致安裝異常終止,此時已放置的數據目錄卻沒有被清除掉。第二次安裝的時候package被分配了新的的uid,但此時已有同名卻不同uid的數據目錄存在,導致uid_changed錯誤,安裝失敗。

            為什么第二次安裝的時候就會被分配不同的uid?關鍵在于 mSettings.getPackageLPw,輾轉ref到/frameworks/base/services/java/com/android/server/pm/Settings.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);//關鍵點
            487                    }
            488                }
            

            繼續查看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    }
            

            mUserIds是一個PackageSettings的數組狀結構,維護了當前的userid,并在安裝時遍歷進行分配。在第一次惡意的占位安裝中, mUserIds這個array狀結構已經被添加了一個PackageSettings進去,形成類似于

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

            的結構,但在dexopt failed的時候最末尾一項沒有被移除。隨后再安裝時,newUserIdLPw會遍歷mUserIds,發現沒有空位,就會在末尾重新添加一個,就會形成

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

            的結構,導致兩次安裝分配的UID不同,觸發INSTALL_FAILED_UID_CHANGED。

            但值得注意的是,這時候mUserIds并沒有被固化在packages.xml和packages.list中。

            0x04 進一步思考


            那么這樣肯定會想到,如果殺掉system_server(軟重啟),讓其重新掃描并建立mUserIds數組不就能修復這個問題了?

            理論上來說,如果在重啟前沒有安裝過其他應用的話,那么這還真是可行的。因為重啟后重新建立的uid數組是[(10001, bla),...,()10069, haha)],那么重新安裝的com.taobao.taobao剛好能占到10070的位置,皆大歡喜。

            但如果在重啟后又安裝了其他應用,那么其就會占掉10070的位置,導致taobao再安裝的時候以10071及之后的uid就拿不回原來應該屬于它的/data/data/com.taoba.taobao了... what a pity.

            以上在stock rom(Genymotion, SDK)和小米2、Nexus等上驗證通過。

            所以現在看來,原作者說只有root或者reset才能清除這個問題的說法似乎不準確,至少從給出的poc和google的diff來看實驗結果某些情況下重啟就能fix。具體還有什么細節就只能等待SPSM的paper了。總體來說,這是一個比較好玩的trick類漏洞,而且從issuelink來看,應該還有一些其他類型的同樣效果的漏洞存在。

            0x05 修復


            Google對此的修復:

            Google的diff主要是添加了SCAN_DELETE_DATA_ON_FAILURES的flag,在設置了該flag的時候安裝失敗時會刪除遺留掉的文件。

            #!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;
                                 }
            

            如何fix某個占位攻擊:

            root下刪除該數據目錄即可,非root。。。那只能reset了。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线