0%

CS35Plus 飞思卡尔车机破解软件安装限制

这不前几天用 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);
    }
    ...
}

经过分析可以发现:

  1. 来到 main 函数的参数,被解析之后发往了 mPm.installPackageWithVerificationAndEncryption() 这个方法,其中 mPmIPackageManager.Stub.asInterface(ServiceManager.getService("package")) 得到的一个跨进程接口,其实就是 PackageManage,好家伙原来 pm 命令和用户界面的流程是一样的!这让我更加肯定就是系统在某处加了统一的拦截。
  2. 我执行 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.jarframework.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 就发现已经完成破解了,还一行代码都没改。
这也给我敲响警钟,平时写代码一定要写得规范一些,免得别人看不懂。。。(话说这是不是他们故意的???)

  • 本文作者: 6x
  • 本文链接: https://6xyun.cn/article/149
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!