概要
本文分两个部分:
1、分析21款凯美瑞车机系统安装软件;
2、浅析OTA升级包,以车机固件v1.000062版本为例。
声明:
1、本文刷机包来源于 西窗浪人分享 https://www.bigxd.com/2023/04/1093.html
2、解包工具来源:https://github.com/bassrock/AVIC-NEX
一、安装软件教程
前言
21款凯美瑞车整体体验尚可,但是对车机不甚满意,尝试各种方法无果,为了这个车机也是操碎了心。无意间发现网上有卖这车机的emmc数据,于是购买了一份来研究。
车机配置
通过解析emmc数据,发现车机硬件配置:采用恩智浦的I.MX6Q处理器,辅以 4GB内存、32GB硬盘,集成4G上网模块,运行深度定制的安卓7.1.2-user版本系统,这个配置在合资车中还算不错。
进入正题
深入分析emmc数据,发现系统脚本有打开和关闭adb 的脚本,脚本比较简单就不具体分析了,代码如下:
打开adb脚本:role_to_adb.sh
#!/bin/bash
#CDP CB1 pin
echo 0 > /sys/class/gpio/gpio145/value
sleep 1
#start adbd
echo 1 > /sys/class/gpio/gpio34/value
setprop sys.usb.config mtp,adb
setprop persist.sys.usb.config adb
setprop sys.usb.config adb
setprop sys.usb.state adb
echo 0 > /sys/class/gpio/gpio111/value
关闭adb的脚本:role_to_dio.sh
#!/bin/bash
#CDP CB1 pin
echo 1 > /sys/class/gpio/gpio145/value
#stop adbd
echo 0 > /sys/class/gpio/gpio34/value
echo 1 > /sys/class/gpio/gpio111/value
通过这两个脚本就可以分析,系统是有打开adb的方法的,只是没发现而已,一般隐藏菜单都是在系统设置里面,于是分析系统app,找到setting.apk,反编译定位到jp.pioneer.ceam.setting.debugmode,具体代码如下:
package jp.pioneer.ceam.setting.debugmode;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import jp.pioneer.ceam.KeyManager.KeyManagerController;
import jp.pioneer.ceam.KeyManager.KeyManagerListener;
/* compiled from: DebugMode_PasswordInput.java */
/* loaded from: classes.dex */
public class U extends Fragment {
/* renamed from: a reason: collision with root package name */
private static String f871a = "DebugMode_PasswordInput";
/* renamed from: b reason: collision with root package name */
private static String f872b = "piodevelopdbg";
/* renamed from: c reason: collision with root package name */
private TextView f873c;
private EditText d;
private Button e = null;
private KeyManagerListener f = new Q(this);
WeakReference<KeyManagerListener> g = new WeakReference<>(this.f);
/* JADX INFO: Access modifiers changed from: private */
public void c() {
Log.d(f871a, "DebugMode_PasswordInput transactionToMainFrag");
try {
FragmentTransaction beginTransaction = getFragmentManager().beginTransaction();
beginTransaction.replace(getId(), (P) P.class.newInstance());
beginTransaction.addToBackStack(null);
beginTransaction.commit();
} catch (IllegalAccessException | InstantiationException unused) {
}
}
@Override // android.app.Fragment
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Log.d(f871a, "DebugMode_PasswordInput onCreate");
}
@Override // android.app.Fragment
public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle bundle) {
Log.d(f871a, "DebugMode_PasswordInput onCreateView");
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-1, -1);
RelativeLayout relativeLayout = new RelativeLayout(getContext());
relativeLayout.setLayoutParams(layoutParams);
this.f873c = new TextView(getContext());
this.f873c.setWidth(200);
this.f873c.setHeight(50);
this.f873c.setX(100.0f);
this.f873c.setY(100.0f);
this.f873c.setTextColor(-1);
this.f873c.setTextSize(20.0f);
relativeLayout.addView(this.f873c);
this.f873c.setGravity(17);
this.f873c.setText("请输入密码:");
this.e = new Button(getContext());
this.e.setX(880.0f);
this.e.setY(100.0f);
this.e.setWidth(150);
this.e.setHeight(50);
relativeLayout.addView(this.e);
this.e.setText("确定");
this.d = new EditText(getContext());
this.d.setX(300.0f);
this.d.setY(100.0f);
this.d.setWidth(580);
this.d.setHeight(50);
relativeLayout.addView(this.d);
this.d.setImeOptions(6);
this.d.setEms(10);
this.d.setSingleLine();
return relativeLayout;
}
@Override // android.app.Fragment
public void onDestroy() {
super.onDestroy();
Log.d(f871a, "DebugMode_PasswordInput onDestroy");
}
@Override // android.app.Fragment
public void onDestroyView() {
super.onDestroyView();
this.f873c = null;
this.d = null;
this.e = null;
Log.d(f871a, "DebugMode_PasswordInput onDestroyView");
}
@Override // android.app.Fragment
public void onPause() {
super.onPause();
Log.d(f871a, "DebugMode_PasswordInput onPause");
}
@Override // android.app.Fragment
public void onResume() {
super.onResume();
Log.d(f871a, "DebugMode_PasswordInput onResume");
this.e.setOnClickListener(new S(this));
this.d.setOnEditorActionListener(new T(this));
}
@Override // android.app.Fragment
public void onStart() {
super.onStart();
KeyManagerController.getInstance(getContext()).registerKeyManagerListener(this.g, -1);
Log.d(f871a, "DebugMode_PasswordInput onStart");
}
@Override // android.app.Fragment
public void onStop() {
super.onStop();
KeyManagerController.getInstance(getContext()).unregisterKeyManagerListener(this.g);
Log.d(f871a, "DebugMode_PasswordInput onStop");
}
}
分析代码就找到了进入诊断模式的密码为:piodevelopdbg ,进一步分析知道,打开车机设置——系统——OSS——系统信息——连续点击蓝牙地址 10 次跳出密码框,输入以上密码就可以进去了。如图:
点击ADB On USB 就可以打开adb调试,满天欢喜使用双公头数据线连接电脑安装软件,给我来了个报错,错误代码如下:
Performing Streamed Install
adb: failed to install D:\BaiduNetdiskDownload\qqmusiccar_1.9.8.22.apk: Failure [INSTALL_FAILED_VERIFICATION_FAILURE: app is not in the whitelist. packageName:com.tencent.qqmusiccar]
居然有白名单验证,查看emmc数据,发现白名单文件在system\etcwhitelistapps.txt里面,得替换系统文件才行,由于是安卓7.1.2系统,有分区验证,打开原生安卓设置,没有oem选项,adb 无法打开fastboot模式。尝试三方root软件无果,目前只能安装白名单里面的软件,或者卸载白名单内三方软件,修改包名(跳转)安装其他三方软件,网上有方法这里就不再赘述。
二、浅析OTA升级包解包打包(未测试通过)
adb方式走不通,网上有大佬分享的刷机包,于是下载来看看。
刷机包格式与安卓常用的刷机包不一样,无法使用常规方式解包,网上一搜,国外有大佬研究过先锋的安卓导航系统,于是下载来学习解包。
解包工具来源:https://github.com/bassrock/AVIC-NEX
根据21款凯美瑞车型刷机文件夹NVF-9108ZT,修改NVF.sh脚本,关键代码如下:
#this function is called when system is not Linux, user is not root, or user did not specify a file
function usage() {
echo "usage:"
echo " NVF.sh /path_to/firmware.zip [NVF Model] [NVF firmware version]"
echo "where NVF Model = 9108"
echo "where NVF firmware version = 190"
exit
}
function generateVERFile() {
originalVerFile=$1
prgFile=$2
dd if="$originalVerFile" of="$originalVerFile.part1" bs=172 skip=0 count=1
SIZE="$(stat -c%s $prgFile)"
echo "$prgFile size is: $SIZE"
UNSWAPPEDHEXSIZE="$(printf '%x\n' $SIZE)"
vSIZE="$UNSWAPPEDHEXSIZE"
SWAPPEDHEXSIZE="${vSIZE:6:2}${vSIZE:4:2}${vSIZE:2:2}${vSIZE:0:2}"
LEN=$(echo ${#SWAPPEDHEXSIZE})
if [ $LEN -eq 6 ]; then
SWAPPEDHEXSIZE=${SWAPPEDHEXSIZE}00
fi
UNSWAPPEDHEXCRC="$(crc32 $prgFile)"
vCRC="$UNSWAPPEDHEXCRC"
SWAPPEDHEXCRC="${vCRC:6:2}${vCRC:4:2}${vCRC:2:2}${vCRC:0:2}"
echo "$prgFile Unswapped prg size hex is: $UNSWAPPEDHEXSIZE"
echo "$prgFile Swapped prg size hex is: $SWAPPEDHEXSIZE"
echo "$prgFile Unswapped prg crc hex is: $UNSWAPPEDHEXCRC"
echo "$prgFile Swapped prg crc hex is: $SWAPPEDHEXCRC"
#perl -e "print pack 'H*', '${defaultheaderver}${SWAPPEDHEXSIZE}${SWAPPEDHEXCRC}A55A5AA5'" > PJ${version}PLT.VERSTART
perl -e "print pack 'H*', '${SWAPPEDHEXSIZE}${SWAPPEDHEXCRC}A55A5AA5'" > "$originalVerFile.part2"
cat "$originalVerFile.part1" "$originalVerFile.part2" > "$originalVerFile.combined"
UNSWAPPEDHEXCRC="$(crc32 $originalVerFile.combined)"
vCRC="$UNSWAPPEDHEXCRC"
SWAPPEDHEXCRC="${vCRC:6:2}${vCRC:4:2}${vCRC:2:2}${vCRC:0:2}"
echo "$originalVerFile.combined Unswapped verstart crc hex is: $UNSWAPPEDHEXCRC"
echo "$originalVerFile.combined Swapped verstart crc hex is: $SWAPPEDHEXCRC"
perl -e "print pack 'H*', '${SWAPPEDHEXCRC}'" > "$originalVerFile.combined1"
cat "$originalVerFile.combined" "$originalVerFile.combined1" > "$originalVerFile.new"
mv "$originalVerFile.new" "$originalVerFile"
}
function generatePRGFile() {
originalPRGFile=$1
imgToMakePRG=$2
dd if=$originalPRGFile of=originalPRGFile.header bs=4 skip=3 count=125
#get image size
# for mac
# SIZE="$(stat -f%z PJ${version}PLT.IMG)"
#for linux
SIZE="$(stat -c%s ${imgToMakePRG})"
echo "${imgToMakePRG} size is: $SIZE"
#We have to swap the hex values from big endian to little endian cause the ZT is arm.
UNSWAPPEDHEXSIZE="$(printf '%x\n' $SIZE)"
vSIZE="$UNSWAPPEDHEXSIZE"
SWAPPEDHEXSIZE="${vSIZE:6:2}${vSIZE:4:2}${vSIZE:2:2}${vSIZE:0:2}"
LEN=$(echo ${#SWAPPEDHEXSIZE})
if [ $LEN -eq 6 ]; then
SWAPPEDHEXSIZE=${SWAPPEDHEXSIZE}00
fi
UNSWAPPEDHEXCRC="$(crc32 ${imgToMakePRG})"
vCRC="$UNSWAPPEDHEXCRC"
SWAPPEDHEXCRC="${vCRC:6:2}${vCRC:4:2}${vCRC:2:2}${vCRC:0:2}"
echo "${imgToMakePRG} Unswapped size hex is: $UNSWAPPEDHEXSIZE"
echo "${imgToMakePRG} Swapped size hex is: $SWAPPEDHEXSIZE"
echo "${imgToMakePRG} Unswapped crc hex is: $UNSWAPPEDHEXCRC"
echo "${imgToMakePRG} Swapped crc hex is: $SWAPPEDHEXCRC"
#create new header
perl -e "print pack 'H*', 'A55A5AA5${SWAPPEDHEXSIZE}${SWAPPEDHEXCRC}'" > firstheaderhalf.header
cat firstheaderhalf.header originalPRGFile.header "$imgToMakePRG" > "$originalPRGFile.new"
rm -rf firstheaderhalf.header
rm -rf originalPRGFile.header
rm -rf "$originalPRGFile"
rm -rf "$imgToMakePRG"
mv "$originalPRGFile.new" "$originalPRGFile"
}
function createImgFile() {
prgFile=$1
newImgFile=$2
dd if="$prgFile" of="$newImgFile" bs=512 skip=1
修改后打包即可。这种方式目前未走通,最后刷机校验有点问题,有时间折腾再续…
小结
**提示: 1.有兴趣的可以继续研究刷机方式,以及反汇编libNPSysCtrlHandler.so 文件,找到正确的TESTMODE.KEY进入工厂模式绕过验证刷机。 2、目前西窗浪人已研究出:一条代码关闭APP白名单配置,任意安装APP**