0%

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

这不前几天用 TTL 把我的车中控车机给破解了嘛,目前装了 嘟嘟桌面高德地图车机版、腾讯地图车机版、QQ音乐车载版酷狗音乐车载版、ES文件浏览器、HDP直播via浏览器等软件。经过几天的摸索,完成了软件的优化配置,已经达到了比较好的体验。

这里着重强调一下 嘟嘟桌面 这个软件,做得很好啊!还免费给用户使用,业界良心。并且能够适配多种地图软件,多种音乐播放器!最最最牛批的是,还是适配了各种车辆的方控,恰好我的车辆的方控也能支持!完美!

不过,这个系统还有一点问题如芒刺背,那就是不能通过用户界面安装软件。由于我没得进行硬解,所以每次装卸软件只能先连接 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);
}
...
}

经过分析可以发现:

  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() 这个方法。

代码如下:

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 就发现已经完成破解了,还一行代码都没改。
这也给我敲响警钟,平时写代码一定要写得规范一些,免得别人看不懂。。。(话说这是不是他们故意的???)

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