唉,一言难尽,我的车车CS75P 二代车机系统终于更新了,上了高德新版, 有了红绿灯倒计时,福音呀,盼了很久,终于更新了。

我就想着将OTA包里面的apk,直接提取出来,升级就行了,不需要更新车机系统了,结果失败!

拿到了心心念的OTA升级包,一打开。

哦豁~~~~

它竟然做了加密的,,,好惨

我就想着,既然这里做了加密,那么车机系统在拿到包后,肯定也需要进行解密才能去安装呀,于似乎带着好奇去看了看

0x1 获取升级apk

在车机交流群里面,顺利拿到了负责车机升级的apk,拖进jadx 里面,找关键字,HuOs

还真找到了。

这不就是我升级的路径,和包格式嘛 .zip

跟着调用栈一路静态分析,结果追到了解密方法:

这里发现 包被进行了 “AES/CBC/PKCS5Padding” 的加密,车机系统拿到文件,进行解密后,再安装

0x2 获取解密Key

又上一个步骤发现,是通过获取 “/system/bin/sd_ivi_data/ckey” 的值,

然后使用 未宸 解密,对这个文件进行解密,获取前16 字节,作为系统包的aes key

想要解密这个就得两个步骤:

1、获取到这个ckey 这个文件 (万能的车友群 把文件给了我)

2、拿到 whiteBox.decrypt 的代码实现逻辑 (万能的车友群 把文件给了我)

步骤2 走了一些弯路

起初以为 whiteBox.decrypt 这个代码的实现是放在 boot-framework.vdex 、最后通过搜索,发现是放在

boot-ext.vdex 里面的。

这里就需要使用两个工具

  1. vdex 转为cdex
  2. 再将 cdex 转为dex

最终打开,

解密方法则是将 文件的值,与一个key ,进到native 里面 计算出来

那么我们还需要获取到这个token :

0x3 获取解密Key的解密token

通过静态分析,获取token的 通过获取升级 apk 下的一些文件值,在native 里面进行判断,最后返回出结果。

起初,我以为需要在native 里面进行计算,最后反编译进去一看,,

这…… 好家伙只是将获取的数据进行了对比,然后直接返回token, 想复杂了。。。。

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
int __fastcall Java_com_weichen_whitebox_encrytion_WhiteBoxNativeImpl_connectionToNativeVerifyDigest(JNIEnv *a1, int a2, int a3, size_t a4, int a5, int a6, size_t a7, int a8, int a9, int a10, int a11)
{
_DWORD *v13; // r9
const char *v14; // r5
const char *v15; // r10
int v16; // r0
const char *v17; // r5
jbyte *v18; // r4
jbyte *v19; // r6
jstring (*v20)(JNIEnv *, const char *); // r2
const char *v21; // r1
const char *v23; // r4
void *v24; // r9
const char *v25; // r10
jbyte *v26; // r4
jbyte *v27; // r5
const char *v28; // [sp+1Ch] [bp-2Ch]
const char *v29; // [sp+24h] [bp-24h]

v13 = malloc(0x20u);
v14 = (*a1)->GetStringUTFChars(a1, a9, 0);
v15 = (*a1)->GetStringUTFChars(a1, a5, 0);
if ( !j_readFileFromApk(v14, v15, v13) )
{
_android_log_print(4, "LeosinAcsctl: digest", "can't find file, LINE = %d", 275);
LABEL_8:
v20 = (*a1)->NewStringUTF;
v21 = "can't find file";
return (int)v20(a1, v21);
}
v29 = v14;
v16 = j_calculateDigest(a1, v13, a10);
if ( !v16 || (v17 = (const char *)v16, !v13[4]) )
{
_android_log_print(4, "LeosinAcsctl: digest", "unsupproted signAlg, LINE = %d", 281);
v20 = (*a1)->NewStringUTF;
v21 = "unsupproted signAlg";
return (int)v20(a1, v21);
}
v18 = (*a1)->GetByteArrayElements(a1, a3, 0);
v19 = (jbyte *)malloc(a4);
qmemcpy(v19, v18, a4);
if ( strncmp(v17, v19, a4) )
{
_android_log_print(4, "LeosinAcsctl: digest", "mainfiestFileDigest is wrong, LINE = %d", 290);
v20 = (*a1)->NewStringUTF;
v21 = "mainfiestFileDigest is wrong";
return (int)v20(a1, v21);
}
(*a1)->ReleaseByteArrayElements(a1, (jbyteArray)a3, v18, 2);
(*a1)->ReleaseStringUTFChars(a1, (jstring)a5, v15);
free(v19);
free(v13);
v23 = (*a1)->GetStringUTFChars(a1, a8, 0);
v24 = malloc(0x20u);
if ( !j_readFileFromApk(v29, v23, v24) )
{
_android_log_print(4, "LeosinAcsctl: digest", "can't find file, LINE = %d", 301);
goto LABEL_8;
}
v28 = v23;
v25 = (const char *)j_calculateDigest(a1, v24, a11);
v26 = (*a1)->GetByteArrayElements(a1, a6, 0);
v27 = (jbyte *)malloc(a4);
qmemcpy(v27, v26, a7);
__android_log_print(4, "LeosinAcsctl: digest", "dexClassDigestLen is %d", a7);
__android_log_print(4, "LeosinAcsctl: digest", "dexClassDigestArr is %s", v27);
__android_log_print(4, "LeosinAcsctl: digest", "dexClassDigest is %s", v25);
if ( !strncmp(v27, v25, a7) )
{
(*a1)->ReleaseByteArrayElements(a1, (jbyteArray)a6, v26, 2);
(*a1)->ReleaseStringUTFChars(a1, (jstring)a8, v28);
free(v27);
free(v24);
(*a1)->ReleaseStringUTFChars(a1, (jstring)a9, v29);
v20 = (*a1)->NewStringUTF;
v21 = "@ABCDEFG";
}
else
{
_android_log_print(4, "LeosinAcsctl: digest", "can't find file, LINE = %d", 313);
v20 = (*a1)->NewStringUTF;
v21 = "dexclass digest is wrong";
}
return (int)v20(a1, v21);
}

0x4 解密Key

前面既然已经拿到了 token ,那我们直接使用Unidbg 将 token 值,和ckey 的值传入,计算出结果即可

1
2
3
4
5
6
public byte[] key() {
String y2 = "decryptUsingNative([BLjava/lang/String;)[B";
//参数1 ckey的值,参数2 token 值
DvmObject<?> dvmObject = UmeJni.callStaticJniMethodObject(emulator, y2, Base64.getDecoder().decode("j3AR4u/J4hedDbN8gkrqbbj7ibVPSX695NCfhVxSQWc="), "@ABCDEFG");
return (byte[]) dvmObject.getValue();
}

0x5 解密

拿到key 了,返回第一步,使用AES/CBC/PKCS5Padding 解密,获得ota包

然后使用 开源工具

payload-dumper-go 将 payload.bin 提取出 system.img 即可:

就拿到了 高德地图

0x6 总结

整体弄下来,花了一下午的时间,最开始想的太难了,动手起来,多亏了 万能的车群 提供了相应的文件进行分析,万事开头难,做起来就顺了。

整体分析难道不大,都是一些基础的调用,和简单的知识点结合就行。