App Ops
Android 4.3 隱藏功能 App Ops 分析
Android 4.3 剛剛發布,大家還在關心功能上有沒有什么亮點的時候,一個隱藏功能被AndroidPolice?報道出來。這個隱藏功能 Google 把它叫做 App Ops (Application Operations),也就是我們常說的權限管理。眾所周知,Android 的權限是飽受詬病的一大安全隱患。在 Android 4.3 面世以前,主要有三種方法對 App 進行權限管理:重打包,patch 和注入。Android 4.3 的 App Ops 這一隱藏功能,一舉毀滅了之前所有的努力。不過,這也算是對之前工作的肯定吧,Google 終于加入權限管理了!本文將結合 Android 4.3 AOSP 代碼,對 App Ops 這一隱藏功能進行詳細分析。
一、背景知識
這一部分介紹了權限管理的相關知識,如果了解的可以跳過。我將簡單介紹幾個概念,提供一個方向,想深入了解的可以自己搜索相關資料。
1. 什么叫權限管理?
Android 的安全保護源于權限,每個 App 需要申請權限才能使用特定的功能,用戶在安裝的時候可以瀏覽 App 已申請的權限再決定是否安裝。但是隨著權限不斷增多和殘酷的現實證明了這一功能基本屬于雞肋。所以就出現了權限管理這一安全防護,所謂權限管理,就是能夠手動配置某個 App 的權限,進而阻止惡意軟件以及防止隱私泄漏。當然,更進一步的權限管理,是能夠在 App 動態使用某個權限的時候,彈窗提示用戶允許和拒絕。這樣的權限管理就更加類似于 Windows 中的主動防御。
實現權限管理主要有三種方法:
當然,Android 4.3 出現 App Ops 以后,隨著新版本的不斷完善,這些方法可能就沒有存在的意義了。
2. 什么是 Binder 通信?
TBA
3. 什么是權限中的保護級別 (Protection Level) 以及系統權限 (System Permission)?
Android 文檔介紹的很清楚,protectionLevel 是 signatureOrSystem 的權限是系統 App 和相同簽名的 App 才可以申請的。
4. 系統服務(System_Server)?
Android 啟動后有很多服務進程提供比本功能,一些系統級的服務由 system_server 這個進程進行處理,其中包括電源管理,電話管理,包管理,藍牙管理等等,詳見Android的系統服務一覽。還有一類我們常用的服務是不包括在系統服務當中的,比如說發短信,GPS等等,是由各自的服務進程處理的。
5. 如何開啟 App Ops?
App Ops 其實就是 Settings 里面的一個 Activity,所以找個方法直接把它打開即可。 有兩種簡單方法:
二、系統架構
1. 架構
App Ops 的基本架構如圖所示:
其中包含兩個重要部分,一個叫做AppOpsService,另外一個叫AppOpsManager。
AppOpsService 是一個系統服務,注冊的名字叫做 "appops",是最終檢查權限的服務,其中權限管理存儲在 appops.xml 文件里面。
AppOpsManager 是一個訪問 AppOps 服務的類,同時有 Java 和 C 的實現,為了應對某些 native code 的服務,比如說 Camera。
Settings 可以使用 AppOpsManger 來讀取和修改權限管理信息。當其他 App 使用某個權限的時候,會通過 Binder 訪問服務端的某項服務。在服務端的各個服務中都插入了檢查權限的代碼,同樣通過使用 AppOpsManger 來檢查權限。
2. 代碼分析
2.1 AppOpsService?代碼在:/frameworks/base/services/java/com/android/server/AppOpsService.java
檢查用戶設定權限的函數是:checkOperation() 和 noteOperation(),區別是 checkOperation() 只是檢查 Operation 的情況,noteOperation() 還會記錄訪問時間等信息,代碼如下:
@Override public int checkOperation(int code, int uid, String packageName) {verifyIncomingUid(uid);verifyIncomingOp(code);synchronized (this) {Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);if (op == null) {return AppOpsManager.MODE_ALLOWED;}return op.mode;} }@Override public int noteOperation(int code, int uid, String packageName) {verifyIncomingUid(uid);verifyIncomingOp(code);synchronized (this) {Ops ops = getOpsLocked(uid, packageName, true);if (ops == null) {if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid+ " package " + packageName);return AppOpsManager.MODE_IGNORED;}Op op = getOpLocked(ops, code, true);if (op.duration == -1) {Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName+ " code " + code + " time=" + op.time + " duration=" + op.duration);}op.duration = 0;final int switchCode = AppOpsManager.opToSwitch(code);final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "+ switchCode + " (" + code + ") uid " + uid + " package " + packageName);op.rejectTime = System.currentTimeMillis();return switchOp.mode;}if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid+ " package " + packageName);op.time = System.currentTimeMillis();op.rejectTime = 0;return AppOpsManager.MODE_ALLOWED;} }修改某個 App 的某項權限的函數是 setMode(),其中就是修改成員變量 mUidOps。mUidOps 是一個List 保存了某個package對應的所有權限的mode (允許,忽略),具體代碼如下:
@Override public void setMode(int code, int uid, String packageName, int mode) {verifyIncomingUid(uid);verifyIncomingOp(code);ArrayList<Callback> repCbs = null;code = AppOpsManager.opToSwitch(code);synchronized (this) {Op op = getOpLocked(code, uid, packageName, true);if (op != null) {if (op.mode != mode) {op.mode = mode;ArrayList<Callback> cbs = mOpModeWatchers.get(code);if (cbs != null) {if (repCbs == null) {repCbs = new ArrayList<Callback>();}repCbs.addAll(cbs);}cbs = mPackageModeWatchers.get(packageName);if (cbs != null) {if (repCbs == null) {repCbs = new ArrayList<Callback>();}repCbs.addAll(cbs);}if (mode == AppOpsManager.MODE_ALLOWED) {// If going into the default mode, prune this op// if there is nothing else interesting in it.if (op.time == 0 && op.rejectTime == 0) {Ops ops = getOpsLocked(uid, packageName, false);if (ops != null) {ops.remove(op.op);if (ops.size() <= 0) {HashMap<String, Ops> pkgOps = mUidOps.get(uid);if (pkgOps != null) {pkgOps.remove(ops.packageName);if (pkgOps.size() <= 0) {mUidOps.remove(uid);}}}}}}scheduleWriteNowLocked();}}}if (repCbs != null) {for (int i=0; i<repCbs.size(); i++) {try {repCbs.get(i).mCallback.opChanged(code, packageName);} catch (RemoteException e) {}}} }修改 mode 之后會通過 writeState() 的函數,最終寫入文件當中,他用 appops.xml 來存儲 App Ops 信息,如果想了解 xml 中的各個 tag 的語義,可以查看 writeState() 函數的實現。 簡單來說他會記錄 uid, 包名,mode,訪問時間,拒絕時間。
2.2 AppOpsManager?是一個管理類來和 AppOpsService 通信,他的函數實現比較簡單,重點是把控制轉移到 AppOpsService 就可以了。例如 noteOperation() 和 setMode() 在 AppOpsManager 里面調用他們的函數是 noteOp() 和 setMode(),代碼如下:
public int noteOp(int op, int uid, String packageName) {try {int mode = mService.noteOperation(op, uid, packageName);if (mode == MODE_ERRORED) {throw new SecurityException("Operation not allowed");}return mode;} catch (RemoteException e) {}return MODE_IGNORED; }public void setMode(int code, int uid, String packageName, int mode) {try {mService.setMode(code, uid, packageName, mode);} catch (RemoteException e) {} }2.3 攔截代碼?有了 noteOp() 函數,但是要完成權限的動態檢查,還要在執行某項權限的時候執行 noteOp()。經過分析,大概有十多個服務被插入了權限檢查函數。其中包括:ClipboardService, VibratorService, LocationManagerService, NotificationManagerService, GeofenceManager, GpsLocationProvider, IccSmsInterfaceManager, PhoneInterfaceManager, OutgoingCallBroadcaster, WifiService, ContentProvider, WindowManagerService 等等。更多詳細情況,可以查看 AndroidXRef 中的源碼。
三、實例分析
我們選取短信相關權限進行分析,看看 App Ops 究竟是如何進行權限控制的。首先我們了解一下發短信的流程,發短信的代碼很簡單,主要分兩個步驟:
SmsManager smsManager=SmsManager.getDefault(); smsManager.sendTextMessage("dest", "src", "content", null, null);第一步是通過 Binder 獲取 Server 段的發送短信服務 isms service。第二部是調用遠端的sendTextMessage() 函數。通過 Proxy Pattern,最后到達實現的方法,位于IccSmsInterfaceManager 中的 sendText()方法,我們先看一下代碼:
@Override public void sendText(String callingPackage, String destAddr, String scAddr,String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {mPhone.getContext().enforceCallingPermission(Manifest.permission.SEND_SMS,"Sending SMS message");if (Rlog.isLoggable("SMS", Log.VERBOSE)) {log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +" text='"+ text + "' sentIntent=" +sentIntent + " deliveryIntent=" + deliveryIntent);}if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),callingPackage) != AppOpsManager.MODE_ALLOWED) {return;}mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent); }遠端 sendText() 函數首先通過 enforceCallingPermission() 函數來檢查 App 是否在 AndroidManifest.xml 中申請了 android.permission.SEND_SMS 的權限。然后在通過調用 mAppOps (AppOpsService 的服務端實例 AppOpsManager) 調用 noteOp() 函數檢查是否通過了用戶的權限設置。很有意思的是,如果沒有通過檢查,就直接 return。所以程序有時候會有提示,有時候假死然后退出。應該說這是 Android 4.3 中 Google 還未完成的代碼,只是暫時完成基本功能而已。
四、結語
此次 Google 在 Android 4.3 中加入了權限管理的功能,雖然代碼還未完善,但是思路和框架設計的比較好,可能不會引出什么新的漏洞。Google 在迎合大眾的需要的同時,故意放出了一個半成品,來測試一下大家的反映。估計照這個效果下去,Android 5 很有可能就能完善這項特性。到那時候我們就真的該考慮,我們還需要 root 么?或許混亂的國內定制機還有他的廣闊市場吧。
總結
- 上一篇: (二十三)【模电】(波形的发生与信号的转
- 下一篇: 用智能TFT液晶模块这种串口屏做产品界面