这不前几天用 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
的源码:
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()
这个方法。
代码如下:
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
包中找到了实现:
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()
是个什么情况呢?看语义好像就是我要找的关键方法啊,运气好这个类也是百度的,终于不用到处找了:
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
就发现已经完成破解了,还一行代码都没改。
这也给我敲响警钟,平时写代码一定要写得规范一些,免得别人看不懂。。。(话说这是不是他们故意的???)