昨天 root 了 reno3,今天就把这个烦人的 usb 安装框给干掉。
代码链接:https://gist.github.com/LeadroyaL/cc1ec2fe8c0818da2436818f1e8dc122
背景
众所周知,oppo对安卓开发者非常不友好,每次 adb install 安装APP都要登录账户输入密码,费时费力,如图所示。
昨天刚 root 了一台 reno3,在拥有 root 权限、安装好 xpsoed 后,我们就可以把这个框给干掉了。
如图所示:
第一步,观察现象
- AndroidStudio 安装 app,弹框输密码;
- AndroidStudio 更新 app,可以正常更新;
- shell 用户安装 app,弹框输密码;
- shell 用户更新 app,可以正常更新;
- root 用户安装 app,直接成功;
1 2
| dumpsys activity top | grep ACTIVITY ACTIVITY com.android.packageinstaller/.OppoPackageInstallerActivity c949e21 pid=6958
|
弹框的Activity 是:
1
| com.android.packageinstaller/.OppoPackageInstallerActivity
|
,我们从这里追。
把 com.android.packageinstaller
拖下来,阅读manifest,发现一个和 adb 相关的receiver。
看起来这三个和我们有关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <activity android:configChanges="0x4a0" android:excludeFromRecents="true" android:name="com.android.packageinstaller.PackageInstallerActivity" android:theme="@style/Theme.PackageInstaller.Translucent"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:scheme="content" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:scheme="package" /> <data android:scheme="content" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.content.pm.action.CONFIRM_INSTALL" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
<activity android:configChanges="0x4a0" android:excludeFromRecents="true" android:name="com.android.packageinstaller.OppoPackageInstallerActivity" android:permission="oppo.permission.OPPO_COMPONENT_SAFE" android:screenOrientation="1" android:theme="@style/Theme.NoAnimation" />
<receiver android:name="com.android.packageinstaller.OppoPackageInstallerReceiver" android:permission="oppo.permission.OPPO_COMPONENT_SAFE"> <intent-filter> <action android:name="oppo.intent.action.OPPO_INSTALL_FROM_ADB" /> </intent-filter>
|
使用 frida 把日志开关打开,
1 2 3 4 5 6 7
| Java.perform(function(){ var cls = Java.use("com.android.packageinstaller.common.k"); cls._a.value = true; cls._b.value = true; cls._c.value = true; cls._d.value = true; });
|
有如下日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 2020-08-24 13:53:41.652 28501-28501/? D/PackageInstaller.AdbInstallerReceiver: OppoPackageInstallerReceiver onReceive, action=oppo.intent.action.OPPO_INSTALL_FROM_ADB 2020-08-24 13:53:41.659 1487-14947/? I/ActivityTaskManager: START u0 {act=android.intent.action.VIEW dat=content://com.oppo.packageinstaller.fileprovider/root-dir/vmdl1662471080.tmp/base.apk typ=application/vnd.android.package-archive flg=0x10000000 cmp=com.android.packageinstaller/.PackageInstallerActivity (has extras)} from uid 10065 and from pid 28501 2020-08-24 13:53:41.675 28501-28501/? W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@e1a2e36 2020-08-24 13:53:41.693 28501-28501/? D/PackageInstaller.PackageInstaller: mOriginatingPackage: com.android.packageinstaller 2020-08-24 13:53:41.694 28501-29214/? I/PackageInstaller: read new cloud config after app updated by sau 2020-08-24 13:53:41.696 28501-28501/? D/PackageInstaller.PackageInstaller: mCallingPkgName: com.android.packageinstaller 2020-08-24 13:53:41.696 28501-28501/? D/PackageInstaller.PackageInstaller: Convert to SCHEME_FILE 2020-08-24 13:53:41.699 1487-13598/? I/ActivityTaskManager: START u0 {act=android.intent.action.VIEW dat=file:///data/app/vmdl1662471080.tmp/base.apk flg=0x12800000 cmp=com.android.packageinstaller/.OppoPackageInstallerActivity (has extras)} from uid 10065 and from pid 28501 2020-08-24 13:53:41.714 28501-29794/? D/PackageInstaller.OppoRomUpdateHelper: local rus xml is the latest version 2020-08-24 13:53:41.719 28501-29794/? D/PackageInstaller: local rus xml is the latest version 2020-08-24 13:53:41.725 28501-28501/? V/PackageInstaller.PerfServiceManager: start perfserviceinit 2020-08-24 13:53:41.726 28501-28501/? V/PackageInstaller.PerfServiceManager: mPerfServiceInited + -1 2020-08-24 13:53:41.727 28501-28501/? D/PackageInstaller: onCreate dataCollection debug info pid 28501 uid 10065 callingPackage com.android.packageinstaller 2020-08-24 13:53:41.727 28501-28501/? D/PackageInstaller.PackageInstaller: mApkSource: 电脑端未知来源
|
可以得到信息:
adb install
后并没有直接开启 OppoPackageInstallerActivity
,而是
- 先收到广播;
- 再启动 PackageInstallerActivity,
- 再启动 OppoPackageInstallerActivity。
这时候有思路,就是收到广播后别开 Activity 了,直接安装不好吗。。。考虑到两个问题,一是回调问题,我不知道这样做 adb install 是否能感知到安装完毕;二是实现问题,我懒得写代码。
观察 root 用户 pm install 的现象,发现一句日志都没有,也没有发广播,说明根本没有走到这里,这正是我需要的功能。
既然root 能做到,我们应该也可以做到,继续追吧。
第三步,分析 framework
根据目前表现来看,oppo 肯定是修改了 framework 的,所以要从 pm install
出发追代码,先把 /system/framework
都拖回来。
先找找 pm 在哪实现的,网上说它代码是在 jar 里的,
1 2 3 4
| adb shell pm --help ... get-moduleinfo [--all | --installed] [module-name] ...
|
搜字符串,
1 2
| ➜ framework grep "get-moduleinfo" * -R Binary file services.jar matches
|
找到 services.jar,阅读代码,大致如下:
- 解析命令
- runInstall 简单处理
- 子函数里访问远程的 IPackageManager,追不到了
代码都在 com.android.server.pm.PackageManagerShellCommand
里
1 2 3 4 5 6 7 8 9 10 11 12 13
| case 1957569947: { if(!cmd.equals("install")) { v2_1 = -1; break; }
v2_1 = 7; break; }
case 7: { return this.runInstall(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private int runInstall() throws RemoteException { PrintWriter pw = this.getOutPrintWriter(); InstallParams params = this.makeInstallParams(); String inPath = this.getNextArg(); this.setParamsSize(params, inPath); int sessionId = this.doCreateSession(params.sessionParams, params.installerPackageName, params.userId); ........ try { int v2_6 = this.doCommitSession(sessionId, false); } catch(Throwable v2) { goto label_70; } ........ }
|
1 2 3 4 5 6 7 8 9
| private int doCreateSession(PackageInstaller.SessionParams arg3, String arg4, int arg5) throws RemoteException { int v5 = this.translateUserId(arg5, true, "runInstallCreate"); if(v5 == -1) { v5 = 0; arg3.installFlags |= 0x40; }
return this.mInterface.getPackageInstaller().createSession(arg3, arg4, v5); }
|
既然涉及到 IPackageManager
,一眼没找到,懒得继续看了,搜别的吧。
我们注意到那个 adb 的广播叫 oppo.intent.action.OPPO_INSTALL_FROM_ADB
,肯定很少见,搜:
1 2
| ➜ framework grep "oppo.intent.action.OPPO_INSTALL_FROM_ADB" * -R Binary file coloros-services.jar matches
|
找到 coloros-services.jar
阅读代码,com.android.server.pm.ColorPackageInstallInterceptManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| private boolean allowSendBroadcastForAdbInstall(Context arg6, String arg7, String arg8, String arg9, IPackageInstallObserver2 arg10, int arg11) { if(!new File(arg9).exists() && !arg9.startsWith("/storage") && !arg9.startsWith("/sdcard")) { return 0; }
Intent intent = new Intent("oppo.intent.action.OPPO_INSTALL_FROM_ADB"); intent.addFlags(0x1000000); intent.putExtra("apkPath", arg9); if(arg7 != null) { intent.putExtra("callerpkg", arg7); }
intent.putExtra("installFlags", arg11); arg6.sendBroadcastAsUser(intent, new UserHandle(ActivityManager.getCurrentUser())); OppoAdbInstallerEntry oaie = OppoAdbInstallerEntry.Builder(arg9, arg10, arg8); ArrayList v3 = this.mOppoPackageInstallerList; synchronized(v3) { this.mOppoPackageInstallerList.add(oaie); return 1; } }
public boolean handleForAdbSessionInstaller(String arg11, String arg12, IPackageInstallObserver2 arg13, int arg14) { if(ColorPackageInstallInterceptManager.EXP_VERSION) {
public boolean allowInterceptAdbInstallInInstallStage(int arg7, PackageInstaller.SessionParams arg8, File arg9, String arg10, IPackageInstallObserver2 arg11) { if((arg8.installFlags & 0x20) != 0 && (arg7 != 0 || arg7 == 0 && !SystemProperties.getBoolean("oppo.root.silent.install", true)) && arg9 != null && arg10 != null) { String path = arg9.getAbsolutePath() + "/base.apk"; Slog.d("ColorPackageInstallInterceptManager", "installStage send adb install pkg:" + arg10 + " path: " + path + " installFlags:" + arg8.installFlags); ArrayMap v4 = this.mPms.mPackages; synchronized(v4) { if((arg8.installFlags & 2) != 0 && (this.mPms.mPackages.containsKey(arg10))) { Slog.d("ColorPackageInstallInterceptManager", "installStage send adb replace install, silent"); return 0; } }
if(this.handleForAdbSessionInstaller(arg10, path, arg11, arg8.installFlags)) { return 1; } }
if((arg8.installFlags & 0x10000000) != 0) { Slog.d("ColorPackageInstallInterceptManager", "installStage from oppo adb installer, set INSTALL_FROM_ADB flag"); arg8.installFlags |= 0x20; arg8.installFlags &= 0xEFFFFFFF; }
return 0; }
|
注意那几句日志,Slog.d,它们是可以在 logcat 里被看到的,因此去日志看看。
shell 安装,弹框,点击取消按钮
1 2
| 2020-08-24 14:27:16.330 1487-1585/? D/ColorPackageInstallInterceptManager: installStage send adb install pkg:com.leadroyal.hellonative path: /data/app/vmdl1622572147.tmp/base.apk installFlags:5242994 2020-08-24 14:27:34.488 1487-1487/? E/ColorPackageInstallInterceptManager: handForAdbSessionInstallerCancel packageName = null !
|
root 安装
无日志
shell 替换安装
1 2
| 2020-08-24 14:39:19.433 1487-1585/? D/ColorPackageInstallInterceptManager: installStage send adb install pkg:com.leadroyal.hellonative path: /data/app/vmdl659442885.tmp/base.apk installFlags:5242994 2020-08-24 14:39:19.433 1487-1585/? D/ColorPackageInstallInterceptManager: installStage send adb replace install, silent
|
显然,hook 掉 allowInterceptAdbInstallInInstallStage
返回false就可以了,写个 xposed 插件,测试,收工!
第四步:编写 xposed 插件
这里主要考虑如何过滤包名,system_server的包名(可能)是 android,过滤一下就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Entry implements IXposedHookLoadPackage { @Override public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (lpparam.packageName.equals("android")) { ClassLoader classLoader = lpparam.classLoader; XposedBridge.log("Patch oppo usb alert START"); XposedHelpers.findAndHookMethod("com.android.server.pm.ColorPackageInstallInterceptManager", classLoader, "allowInterceptAdbInstallInInstallStage", int.class, "android.content.pm.PackageInstaller$SessionParams", "java.io.File", "java.lang.String", "android.content.pm.IPackageInstallObserver2", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); param.setResult(false); } }); XposedBridge.log("Patch oppo usb alert END"); } } }
|
舒服,再也不用看到这个傻逼的框了。