設(shè)備:VIVO X20A 系統(tǒng):8.1.0 ---- 以下調(diào)研針對(duì)該設(shè)備型號(hào),不同的VIVO手機(jī)表現(xiàn)可能會(huì)有不同
前言
VIVO系統(tǒng)添加了自啟動(dòng)管理蕾各,為了能夠跳轉(zhuǎn)到指定頁面引導(dǎo)用戶開啟禾唁。
BgStartUpManagerActivity
該Activity如上圖頁面所示,可通過以下方式進(jìn)入:
設(shè)置→更多設(shè)置→權(quán)限管理→權(quán)限→自啟動(dòng)
通過查看當(dāng)前Activity可以獲得其ComponentName:
com.vivo.permissionmanager/.activity.BgStartUpManagerActivity
然后通過該ComponentName啟動(dòng)該Activity涎拉,代碼如下:
try {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName componentName = ComponentName.unflattenFromString("com.vivo.permissionmanager/.activity.BgStartUpManagerActivity");
intent.setComponent(componentName);
context.startActivity(intent);
} catch (Exception e) {
LogUtil.e("CZKGO", e.getMessage());
}
啟動(dòng)失敗途茫,可以看到如下日志:
CZKGO: Permission Denial: starting Intent { flg=0x10000000 cmp=com.vivo.permissionmanager/.activity.BgStartUpManagerActivity } from ProcessRecord{97cc72b 8345:com.czkgo.test/u0a784} (pid=8345, uid=10784) requires com.vivo.permission.manage.permission.ACCESS
可以看到缺少com.vivo.permission.manage.permission.ACCESS
權(quán)限碟嘴,嘗試在Manifest中添加該權(quán)限后,依然啟動(dòng)失敗并拋出相同錯(cuò)誤囊卜,說明該權(quán)限第三方應(yīng)用不能獲取娜扇。
SoftPermissionDetailActivity
自啟開關(guān)還可以通過SoftPermissionDetailActivity去開啟,該Activity如上圖頁面所示栅组,其進(jìn)入方式為:
設(shè)置→更多設(shè)置→權(quán)限管理→應(yīng)用→(在列表中找到當(dāng)前應(yīng)用)→單項(xiàng)權(quán)限設(shè)置
通過查看當(dāng)前Activity可以獲得其ComponentName:
com.vivo.permissionmanager/.activity.SoftPermissionDetailActivity
通過該ComponentName啟動(dòng)該Activity雀瓢,可以看到該界面一閃而過(屏幕黑一下)但沒有錯(cuò)誤日志,猜測(cè)這是因?yàn)闆]有傳入當(dāng)前應(yīng)用包名作為標(biāo)識(shí)玉掸,畢竟安卓的所有應(yīng)用共用SoftPermissionDetailActivity來管理權(quán)限刃麸。
獲取PermissionManager.apk
以上兩個(gè)Activity屬于包名com.vivo.permissionmanager的應(yīng)用中,通過adb shell pm path com.vivo.permissionmanager
獲取到該APK地址:
package:/system/app/PermissionManager/PermissionManager.apk
在通過adb pull /system/app/PermissionManager/PermissionManager.apk
獲取到PermissionManager.apk
排截。
使用Jadx反編譯該APK嫌蚤,這時(shí)候發(fā)現(xiàn)辐益,該APK中沒有代碼,再找到AndroidManifest脱吱,關(guān)注到如下三條信息:
<permission
android:name="com.vivo.permission.manage.permission.ACCESS"
android:label="access permission"
android:protectionLevel="signatureOrSystem" />
...
...
<activity
android:name=".activity.BgStartUpManagerActivity"
android:exported="true"
android:permission="com.vivo.permission.manage.permission.ACCESS"
android:screenOrientation="portrait"
android:theme="@style/TitleStyle" />
...
...
<activity
android:name=".activity.SoftPermissionDetailActivity"
android:configChanges="mcc|mnc"
android:screenOrientation="portrait"
android:theme="@style/TitleStyle">
<intent-filter>
<action android:name="permission.intent.action.softPermissionDetail" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
可以看到BgStartUpManagerActivity確實(shí)需要com.vivo.permission.manage.permission.ACCESS
權(quán)限智政,并且該權(quán)限被標(biāo)記為signatureOrSystem
,而SoftPermissionDetailActivity不需要權(quán)限.
獲取odex箱蝠,vdex
odex文件是虛擬機(jī)對(duì)解壓出來的dex文件做一定程度的優(yōu)化续捂,文件大小會(huì)減少,并且ODEX 文件比 DEX 文件更難反編譯宦搬,這也在一定程度上提高了安全性牙瓢,因此一些系統(tǒng)預(yù)裝或系統(tǒng)級(jí)應(yīng)用大多采用了 ODEX 優(yōu)化。Android 8.0在odex的基礎(chǔ)上又引入了vdex機(jī)制间校,目的是為了降低dex2oat時(shí)間矾克。
在/system/app/PermissionManager目錄下除了PermissionManager.apk,還可以找到一個(gè)oat文件夾憔足,同樣通過pull命令獲取其中的PermissionManager.odex胁附,PermissionManager.vdex文件:
adb pull /system/app/PermissionManager/oat/arm64/PermissionManager.odex
adb pull /system/app/PermissionManager/oat/arm64/PermissionManager.vdex
轉(zhuǎn)dex
odex,vdex轉(zhuǎn)為dex需要2個(gè)工具jar:smali.jar 和 baksmali.jar:
- smali.jar:smali文件轉(zhuǎn)為dex文件
- baksmali.jar:odex文件轉(zhuǎn)為smali文件
這兩個(gè)工具可以通過https://bitbucket.org/JesusFreke/smali/downloads/網(wǎng)站獲取滓彰。
首先通過baksmali.java將odex文件轉(zhuǎn)為smali文件:
java -jar baksmali.jar deodex PermissionManager.odex
這時(shí)候會(huì)報(bào)錯(cuò):
org.jf.dexlib2.analysis.ClassPathResolver$ResolveException: org.jf.dexlib2.analysis.ClassPathResolver$NotFoundException: Could not find classpath entry boot.oat
這是因?yàn)橄到y(tǒng)APK都會(huì)依賴framwork層一些公共功能庫(kù)控妻,需要到framwork層取到對(duì)應(yīng)oat文件即可:
adb pull /system/framework/arm64/boot.oat
結(jié)果又報(bào)錯(cuò)誤了:
org.jf.dexlib2.DexFileFactory$DexFileNotFoundException: Could not locate the embedded dex file /system/framework/com.qualcomm.qti.camera.jar. Is the vdex file missing?
我們需要將同名的vdex文件也獲取到:
adb pull /system/framework/arm64/boot.vdex
但是再次執(zhí)行java -jar baksmali.jar deodex PermissionManager.odex
還是報(bào)錯(cuò),又顯示缺失其他oat文件揭绑,畢竟PermissionManager依賴很多庫(kù)弓候,干脆用adb pull /system/framework/arm64
將整個(gè)arm64
內(nèi)的文件全都取到在執(zhí)行,成功后會(huì)生成out目錄他匪,目錄中均是smali文件菇存。然后通過smali.jar將其轉(zhuǎn)成class.dex
java -jar smali.jar assemble out/ -o class.dex
查看SoftPermissionDetailActivity源碼
通過Jadx打開SoftPermissionDetailActivity,其onCreate方法如下:
可以看到邦蜜,該Activity通過
packagename
傳遞包名撰筷,如果獲取到包名為null,執(zhí)行finish方法畦徘,所以在跳轉(zhuǎn)時(shí)添加:
intent.putExtra("packagename", context.getPackageName());
這次成功打開了該頁面。
PermissionProvider
現(xiàn)在可以成功跳轉(zhuǎn)頁面了抬闯,但跳轉(zhuǎn)過去后也可能發(fā)現(xiàn)已經(jīng)授予了自啟權(quán)限井辆,這就需要判斷自啟權(quán)限是否已經(jīng)授予。在上文中的Manifest文件中搜索provider溶握,找到了PermissionProvider:
<provider
android:name=".provider.PermissionProvider"
android:authorities="com.vivo.permissionmanager.provider.permission"
android:exported="true"
android:writePermission="com.vivo.permissionmanager.provider.write" />
可以看出exported="true"
表示該provider對(duì)外暴露杯缺,writePermission
則限制了其寫入,在找到PermissionProvider源碼睡榆,其中有如下代碼:
猜測(cè)"bg_start_up_apps"就是控制自啟管理的萍肆,在PermissionProvider中搜索它袍榆,找到:
根據(jù)l111l11111l找到bg_start_up_apps的參數(shù)(你也許會(huì)遇到多個(gè)又l和1組成的參數(shù),注意區(qū)分不要混淆了):
這里我們留意pkgname和currentstate參數(shù)塘揣,通過測(cè)試可知currentstate在有自啟權(quán)限時(shí)值為1包雀,沒有時(shí)值為0,從而完成判斷權(quán)限是否授予方法:
private static boolean isVIVOBgStartPermissionGet(Context context) {
String packageName = context.getPackageName();
Uri uri2 = Uri.parse("content://com.vivo.permissionmanager.provider.permission/bg_start_up_apps");
String selection = "pkgname = ?";
String[] selectionArgs = new String[]{packageName};
try {
Cursor cursor = context
.getContentResolver()
.query(uri2, null, selection, selectionArgs, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int currentstate = cursor.getInt(cursor.getColumnIndex("currentstate"));
cursor.close();
return currentstate == 1;
} else {
cursor.close();
return false;
}
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return false;
}