这不前几天用 TTL 把我的车中控车机给破解了嘛,目前装了 嘟嘟桌面、高德地图车机版、QQ音乐车载版、酷狗音乐车载版、ES文件浏览器、HDP直播、X浏览器等软件。经过几天的摸索,完成了软件的优化配置,已经达到了比较好的体验。
这里着重强调一下 嘟嘟桌面 这个软件,做得很好啊!还免费给用户使用,业界良心。并且能够适配多种地图软件,多种音乐播放器!最最最牛批的是,还是适配了各种车辆的方控,恰好我的车辆的方控也能支持!完美!
不过,这个系统还有一点问题如芒刺背,那就是不能通过用户界面安装软件。由于我没得进行硬解,所以每次装卸软件只能先连接 WiFi 通过远程 adb 来操作,十分不友好!
我也试了使用 adb 终端的 pm 命令,也是不能。但是给了我一条很重要的信息 “INSTALL_FAILED_INTERNAL_ERROR”,他不是常见的什么签名冲突啊,版本不匹配啊之类的,来自程序员的直觉告诉我这系统是故意设计成这样的。
简单来说就是我认为这不是真的故障,而是厂家为了保护系统故意设计成不可安装,原因是在某处设置了障碍。
定位原因
我们知道,通过用户界面安装软件都是调用的 PackageManager
,但用户界面安装软件时系统给的提示并不多,我决定从 pm
命令下手。
由于我是做 Android
软件开发的,不是 Framework
开发,所以刚开始我以为 pm
命令是一个二进制文件,通过一个偶然的机会,我了解到原来 pm
是一个脚本,他内部其实是启动了一个虚拟机进程去执行了 /system/framework/pm.jar
这个 JAR
包。刚好呢我又要我这个车机的固件安装包,里面包含了整个系统文件,于是我提着我的 jadx 就上了。
顺利反编译出了 pm.jar
的源码:
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
| public final class Pm { ... public void run(String[] args) { boolean validCommand = false; if (args.length < 1) { showUsage(); return; } this.mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user")); this.mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); if (this.mPm == null) { System.err.println(PM_NOT_RUNNING_ERR); return; } this.mArgs = args; String op = args[0]; this.mNextArg = 1; if ("list".equals(op)) { runList(); } else if ("path".equals(op)) { runPath(); } else if ("dump".equals(op)) { runDump(); } else if ("install".equals(op)) { runInstall(); } else if ("uninstall".equals(op)) { runUninstall(); } ... } ... private void runInstall() { ContainerEncryptionParams encryptionParams; Uri originatingURI; Uri referrerURI; Uri verificationURI; SecretKey macSecretKey; int installFlags = 64; String installerPackageName = null; String algo = null; byte[] iv = null; byte[] key = null; String macAlgo = null; byte[] macKey = null; byte[] tag = null; String originatingUriString = null; String referrer = null; while (true) { String opt = nextOption(); if (opt == null) { if (algo == null && iv == null && key == null && macAlgo == null && macKey == null && tag == null) { encryptionParams = null; } else if (algo == null || iv == null || key == null) { System.err.println("Error: all of --algo, --iv, and --key must be specified"); return; } else if (!(macAlgo == null && macKey == null && tag == null) && (macAlgo == null || macKey == null || tag == null)) { System.err.println("Error: all of --macalgo, --mackey, and --tag must be specified"); return; } else { try { SecretKey encKey = new SecretKeySpec(key, "RAW"); if (macKey == null || macKey.length == 0) { macSecretKey = null; } else { macSecretKey = new SecretKeySpec(macKey, "RAW"); } encryptionParams = new ContainerEncryptionParams(algo, new IvParameterSpec(iv), encKey, macAlgo, (AlgorithmParameterSpec) null, macSecretKey, tag, -1, -1, -1); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); return; } } if (originatingUriString != null) { originatingURI = Uri.parse(originatingUriString); } else { originatingURI = null; } if (referrer != null) { referrerURI = Uri.parse(referrer); } else { referrerURI = null; } String apkFilePath = nextArg(); System.err.println("\tpkg: " + apkFilePath); if (apkFilePath != null) { Uri apkURI = Uri.fromFile(new File(apkFilePath)); String verificationFilePath = nextArg(); if (verificationFilePath != null) { System.err.println("\tver: " + verificationFilePath); verificationURI = Uri.fromFile(new File(verificationFilePath)); } else { verificationURI = null; } PackageInstallObserver obs = new PackageInstallObserver(); try { this.mPm.installPackageWithVerificationAndEncryption(apkURI, obs, installFlags, installerPackageName, new VerificationParams(verificationURI, originatingURI, referrerURI, -1, (ManifestDigest) null), encryptionParams); synchronized (obs) { while (!obs.finished) { try { obs.wait(); } catch (InterruptedException e2) { } } if (obs.result == 1) { System.out.println("Success"); } else { System.err.println("Failure [" + installFailureToString(obs.result) + "]"); } } return; } catch (RemoteException e3) { System.err.println(e3.toString()); System.err.println(PM_NOT_RUNNING_ERR); return; } } else { System.err.println("Error: no package specified"); return; } } else if (opt.equals("-l")) { installFlags |= 1; } else if (opt.equals("-r")) { installFlags |= 2; } else if (opt.equals("-i")) { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); return; } } else if (opt.equals("-t")) { installFlags |= 4; } else if (opt.equals("-s")) { installFlags |= 8; } else if (opt.equals("-f")) { installFlags |= 16; } else if (opt.equals("-d")) { installFlags |= 128; } else if (opt.equals("--algo")) { algo = nextOptionData(); if (algo == null) { System.err.println("Error: must supply argument for --algo"); return; } } else if (opt.equals("--iv")) { iv = hexToBytes(nextOptionData()); if (iv == null) { System.err.println("Error: must supply argument for --iv"); return; } } else if (opt.equals("--key")) { key = hexToBytes(nextOptionData()); if (key == null) { System.err.println("Error: must supply argument for --key"); return; } } else if (opt.equals("--macalgo")) { macAlgo = nextOptionData(); if (macAlgo == null) { System.err.println("Error: must supply argument for --macalgo"); return; } } else if (opt.equals("--mackey")) { macKey = hexToBytes(nextOptionData()); if (macKey == null) { System.err.println("Error: must supply argument for --mackey"); return; } } else if (opt.equals("--tag")) { tag = hexToBytes(nextOptionData()); if (tag == null) { System.err.println("Error: must supply argument for --tag"); return; } } else if (opt.equals("--originating-uri")) { originatingUriString = nextOptionData(); if (originatingUriString == null) { System.err.println("Error: must supply argument for --originating-uri"); return; } } else if (opt.equals("--referrer")) { referrer = nextOptionData(); if (referrer == null) { System.err.println("Error: must supply argument for --referrer"); return; } } else { System.err.println("Error: Unknown option: " + opt); return; } } } ... private String installFailureToString(int result) { Field[] fields = PackageManager.class.getFields(); for (Field f : fields) { if (f.getType() == Integer.TYPE) { int modifiers = f.getModifiers(); if (!((modifiers & 16) == 0 || (modifiers & 1) == 0 || (modifiers & 8) == 0)) { String fieldName = f.getName(); if (fieldName.startsWith("INSTALL_FAILED_") || fieldName.startsWith("INSTALL_PARSE_FAILED_")) { try { if (result == f.getInt(null)) { return fieldName; } } catch (IllegalAccessException e) { } } } } } return Integer.toString(result); } ... }
|
经过分析可以发现:
- 来到
main
函数的参数,被解析之后发往了 mPm.installPackageWithVerificationAndEncryption()
这个方法,其中 mPm
是 IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
得到的一个跨进程接口,其实就是 PackageManage
,好家伙原来 pm 命令和用户界面的流程是一样的!这让我更加肯定就是系统在某处加了统一的拦截。
- 我执行 pm 命令时看到的错误信息
INSTALL_FAILED_INTERNAL_ERROR
就是 installFailureToString
这个方法给返回来的,他是通过错误码然后反射 android.content.pm.PackageManager
类得到的字段名字,然后显示到终端的。
那我首先去找 INSTALL_FAILED_INTERNAL_ERROR
对应的错误码是多少,再来分析大概是因为什么原因给拦截的。经过一番倒贴最终在 /system/framework/framework.jar
找到了 android.content.pm.PackageManager
这个类,遗憾的是这个类是一个抽象方法,基本没什么业务代码。同时也确认了 INSTALL_FAILED_INTERNAL_ERROR
对应的状态码是 -110,仔细一看 Pm 类当中并没有哪里回掉了 -110 的状态码。那就只能继续跟 mPm.installPackageWithVerificationAndEncryption()
这个方法了。
那问题来了,上面的 mPm
成员变量是 IPackageManager
这个接口的一个实例,并且是系统服务,整个 pm.jar
和 framework.jar
并没有这个类的实现,该上哪去找他的实现类呢?
顺这个 IPackageManager
的包信息 android.content.pm.IPackageManager
,我判断他应该还是在某个系统应用内,于是上网搜索一遍,找到一些有用的信息,原来 IPackageManager
的实现类是一个叫 PackageManagerService
的类,但遗憾的是我没有找到这个类的包信息。没办法,只能自己找了。
经过一番遍历,我在 /system/framework/services.jar
中找到了 com.android.server.pm.PackageManagerService
这个类,刚好就是 IPackageManager.Stub
的实现类,也实现了 installPackageWithVerificationAndEncryption()
这个方法。
代码如下:
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
| public class PackageManagerService extends IPackageManager.Stub { ... public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags) { installPackage(packageURI, observer, flags, null); }
public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName) { installPackageWithVerification(packageURI, observer, flags, installerPackageName, null, null, null); }
public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { installPackageWithVerificationAndEncryption(packageURI, observer, flags, installerPackageName, new VerificationParams(verificationURI, (Uri) null, (Uri) null, -1, manifestDigest), encryptionParams); }
public void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { UserHandle user; int filteredFlags; this.mContext.enforceCallingOrSelfPermission("android.permission.INSTALL_PACKAGES", null); int uid = Binder.getCallingUid(); if (isUserRestricted(UserHandle.getUserId(uid), "no_install_apps")) { try { observer.packageInstalled("", -111); } catch (RemoteException e) { } } else { if ((flags & 64) != 0) { user = UserHandle.ALL; } else { user = new UserHandle(UserHandle.getUserId(uid)); } if (uid == SHELL_UID || uid == 0) { filteredFlags = flags | 32; } else { filteredFlags = flags & -33; } verificationParams.setInstallerUid(uid); Message msg = this.mHandler.obtainMessage(5); msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName, verificationParams, encryptionParams, user); if (this.mSecurity == null || !this.mSecurity.installPackageWithVerificationAndEncryption(packageURI, observer, flags, installerPackageName, verificationParams, encryptionParams)) { this.mHandler.sendMessage(msg); } else { ((InstallParams) msg.obj).serviceError(); } } } ... }
|
那就来看看是哪里返回了 -110
吧,相关流程当中只有 ((InstallParams) msg.obj).serviceError()
会返回 -110
,那么就是上面的 mSecurity == null || !mSecurity.installPackageWithVerificationAndEncryption()
结果为 false。
通过分析可以判断出来,前半段 mSecurity == null
不可能为 true
,那问题的关键就是这个 mSecurity.installPackageWithVerificationAndEncryption()
方法了,看看他为啥返回了 false
。
仔细一看 mSecurity
的包名是 com.baidu.bdcarsec.BdPackageManager
,惊叹竟然好家伙这还有百度的事情,然后呢这个类不在 scrvices.jar
当中,又得找。因为它包名特殊,很容易就在 /system/framework/bdcarsec.jar
包中找到了实现:
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
| public class BdPackageManager { static final String TAG = "BdPackageManager"; Context mContext;
public void init(Context context) { this.mContext = context; BdSecurityService.getInstance().init(this.mContext); }
public boolean deletePackageX(String packageName, int uid, int flags) { if (!SecurityTuning.pm_uninstall_enabled()) { return false; } Slog.d(TAG, "uninstall package : " + packageName); return BdSecurityService.onUninstallPackage(packageName, uid); }
public boolean installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { if (!SecurityTuning.pm_install_enabled()) { return false; } Slog.d(TAG, "InstallPackage : " + packageURI + ", Installer : " + installerPackageName); return BdSecurityService.onInstallPackage(packageURI, installerPackageName); } }
|
前面提到目前分析结果是不知道什么原因,installPackageWithVerificationAndEncryption()
结果为 false
。看到这就比较清晰了,这个 SecurityTuning.pm_install_enabled()
是个什么情况呢?看语义好像就是我要找的关键方法啊,运气好这个类也是百度的,终于不用到处找了:
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
| class SecurityTuning { SecurityTuning() { }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean pm_uninstall_enabled() { return "1".equals(SystemProperties.get("bdcarsec.pm.uninstall", "1")); }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean pm_install_enabled() { return "1".equals(SystemProperties.get("bdcarsec.pm.install", "1")); }
static boolean am_run_enabled() { return "1".equals(SystemProperties.get("bdcarsec.am.run", "1")); }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean am_selfish_enabled() { return "1".equals(SystemProperties.get("bdcarsec.am.selfish", "1")); }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean am_run_verifyjnilibs_enabled() { return "1".equals(SystemProperties.get("bdcarsec.am.run.verifyjnilibs", "1")); }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean am_run_fixjnilibs_enabled() { return "1".equals(SystemProperties.get("bdcarsec.am.run.fixjnilibs", "1")); }
/* JADX INFO: Access modifiers changed from: package-private */ public static boolean am_run_verifyprocess_enabled() { return "1".equals(SystemProperties.get("bdcarsec.am.run.verifyprocess", "1")); } }
|
看到这喜出望外啊,原来他是读了一个开关属性 bdcarsec.pm.uninstall
,如果这个值为 1
,就会导致前面 SecurityTuning.pm_install_enabled()
返回为 false,并且默认这个值默认为 1
,相当于固定返回为 true
。于是我马上去车上用 adb
给 /system/build.prop
加了 bdcarsec.pm.uninstall=0
一句参数,然后用 getprop
命令读了一下试试,发现没生效,于是重启再读,发现生效了,于是测试安装软件,果然就能正常安装了!
其实这是精简版了,实际情况是我第一次分析的时候,逻辑分析反了,认为 bdcarsec.pm.uninstall
这个值为 1
时是关键,但是测试发现过不了,后面还往下分析了 BdSecurityService.onInstallPackage()
的执行过程,又是一个大坑,BdSecurityService.onInstallPackage()
这个方法最终会调用到一个百度的 apk
里面的一个标准 Server
上面,也是一个跨进程服务化调用,并且这个 apk 被混淆过,看得很吃力。
看了一遍之后想不明白,又去车上做实验抓日志,最后通过对比 logcat
日志时才发现我先前的分析逻辑弄反了。最终喜出望外的给 bdcarsec.pm.uninstall
置为 0
就发现已经完成破解了,还一行代码都没改。
这也给我敲响警钟,平时写代码一定要写得规范一些,免得别人看不懂。。。(话说这是不是他们故意的???)