博主头像
mxd's Blog

以技术为翼,以生活为魂

TCL/雷鸟电视 adb 禁止安装 APK 研究

探索

型号:

Project Name: 65S515C PRO
Panel: ST6451D02-3
软件版本: V8-T652T03-LF1V186

底层平台属于 TCL/雷鸟 系列常见的 T652 平台。

问题:

$ adb shell pm install /sdcard/Download/kodi-21.3-Omega-arm64-v8a.apk 
Get install_switch_flag: 0 
install apk has be disabled from pm by system default!

尝试过的:

adb shell settings put system install_switch_flag 1 
adb shell settings put secure install_switch_flag 1
adb shell settings put global install_switch_flag 1
#均正常
adb shell settings get system install_switch_flag
1

adb shell settings get secure install_switch_flag
1

adb shell settings get global install_switch_flag
1

经过尝试依旧一样,无果
settings put global install_switch_flag 1 确实写成功了
settings get global install_switch_flag 返回 1
但是 pm install 仍然打印:

Get install_switch_flag: 0
install apk has be disabled from pm by system default!

这说明 pm 根本不是从 Android Settings.Global 读取这个值。
否则它应该打印 1
也就是说,它调用的是 TCL 自己的配置接口,只是碰巧名字也叫 install_switch_flag

接着,我尝试了:

mxdyeah@MXD-PC-2
$ adb shell content query --uri content://com.tcl.providers.config
No result found.

mxdyeah@MXD-PC-2
$ adb shell content query --uri content://com.tcl.providers.config/config
No result found.

mxdyeah@MXD-PC-2
$ adb shell content query --uri content://com.tcl.providers.config/settings
No result found.

mxdyeah@MXD-PC-2
$ adb shell content query --uri content://com.tcl.providers.config/global
No result found.

mxdyeah@MXD-PC-2
$ adb shell dumpsys package com.tcl.packageinstaller.service.renew | grep Activity
# 无回显

先做些检查:

PS D:\app> adb shell which pm
/system/bin/pm
PS D:\app> adb shell ls -l $(adb shell which pm)
-rwxr-xr-x 1 root shell 34 2009-01-01 00:00 /system/bin/pm
PS D:\app> adb shell pm path android
package:/system/framework/framework-res.apk
PS D:\app> adb shell ls -l /system/framework/services.jar
-rw-r--r-- 1 root root 5035118 2009-01-01 00:00 /system/framework/services.jar
PS D:\app> adb shell ls -l /system/framework/oat
total 4
drwxr-xr-x 2 root root 4096 2009-01-01 00:00 arm

确认了,service.jar/system/framework/services.jar
提取出来,反编译,找到了:

private int runInstall() throws RemoteException {
        PrintWriter pw = getOutPrintWriter();
        InstallParams params = makeInstallParams();
        String inPath = getNextArg();
        String install_switch_flag = SystemProperties.get("persist.tcl.debug.installapk", "0");
        pw.println("Get install_switch_flag: " + install_switch_flag);
        if (!"1".equals(install_switch_flag)) {
            pw.println("install apk has be disabled from pm by system default!");
            return 1;
        }
        pw.println("install apk has be enabled from pm by user!");
        setParamsSize(params, inPath);
        int sessionId = doCreateSession(params.sessionParams, params.installerPackageName, params.userId);
        if (inPath == null) {
            try {
                if (params.sessionParams.sizeBytes == -1) {
                    pw.println("Error: must either specify a package size or an APK file");
                    return 1;
                }
            } finally {
                if (1 != 0) {
                    try {
                        doAbandonSession(sessionId, false);
                    } catch (Exception e) {
                    }
                }
            }
        }
        if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", false) != 0) {
            if (1 != 0) {
                try {
                    doAbandonSession(sessionId, false);
                } catch (Exception e2) {
                }
            }
            return 1;
        }
        if (doCommitSession(sessionId, false) != 0) {
            if (1 != 0) {
                try {
                    doAbandonSession(sessionId, false);
                } catch (Exception e3) {
                }
            }
            return 1;
        }
        pw.println("Success");
        if (0 != 0) {
            try {
                doAbandonSession(sessionId, false);
            } catch (Exception e4) {
            }
        }
        return 0;
    }
private int runInstallCreate() throws RemoteException {
        PrintWriter pw = getOutPrintWriter();
        InstallParams installParams = makeInstallParams();
        String install_switch_flag = SystemProperties.get("persist.tcl.debug.installapk", "0");
        pw.println("Get install_switch_flag: " + install_switch_flag);
        if ("1".equals(install_switch_flag)) {
            pw.println("install apk has be enabled from pm by user!");
            int sessionId = doCreateSession(installParams.sessionParams, installParams.installerPackageName, installParams.userId);
            pw.println("Success: created install session [" + sessionId + "]");
            return 0;
        }
        pw.println("install apk has be disabled from pm by system default!");
        return 1;
    }

    private int runInstallWrite() throws RemoteException {
        long sizeBytes = -1;
        while (true) {
            String opt = getNextOption();
            if (opt != null) {
                if (opt.equals("-S")) {
                    sizeBytes = Long.parseLong(getNextArg());
                } else {
                    throw new IllegalArgumentException("Unknown option: " + opt);
                }
            } else {
                int sessionId = Integer.parseInt(getNextArg());
                String splitName = getNextArg();
                String path = getNextArg();
                return doWriteSplit(sessionId, path, sizeBytes, splitName, true);
            }
        }
    }

    private int runInstallRemove() throws RemoteException {
        PrintWriter pw = getOutPrintWriter();
        int sessionId = Integer.parseInt(getNextArg());
        String splitName = getNextArg();
        if (splitName == null) {
            pw.println("Error: split name not specified");
            return 1;
        }
        return doRemoveSplit(sessionId, splitName, true);
    }

    private int runInstallExisting() throws RemoteException {
        PrintWriter pw = getOutPrintWriter();
        int userId = 0;
        int userId2 = 0;
        while (true) {
            String opt = getNextOption();
            if (opt != null) {
                byte b = -1;
                int iHashCode = opt.hashCode();
                if (iHashCode != -951415743) {
                    if (iHashCode != 1051781117) {
                        if (iHashCode != 1333024815) {
                            if (iHashCode == 1333469547 && opt.equals("--user")) {
                                b = 0;
                            }
                        } else if (opt.equals("--full")) {
                            b = 3;
                        }
                    } else if (opt.equals("--ephemeral")) {
                        b = 1;
                    }
                } else if (opt.equals("--instant")) {
                    b = 2;
                }
                switch (b) {
                    case 0:
                        userId = UserHandle.parseUserArg(getNextArgRequired());
                        break;
                    case 1:
                    case 2:
                        int installFlags = userId2 | 2048;
                        userId2 = installFlags & (-16385);
                        break;
                    case 3:
                        int installFlags2 = userId2 & (-2049);
                        userId2 = installFlags2 | 16384;
                        break;
                    default:
                        pw.println("Error: Unknown option: " + opt);
                        return 1;
                }
            } else {
                String packageName = getNextArg();
                if (packageName != null) {
                    try {
                        int res = this.mInterface.installExistingPackageAsUser(packageName, userId, userId2, 0);
                        if (res == -3) {
                            throw new PackageManager.NameNotFoundException("Package " + packageName + " doesn't exist");
                        }
                        pw.println("Package " + packageName + " installed for user: " + userId);
                        return 0;
                    } catch (PackageManager.NameNotFoundException | RemoteException e) {
                        pw.println(e.toString());
                        return 1;
                    }
                }
                pw.println("Error: package name not specified");
                return 1;
            }
        }
    }

    private int runSetInstallLocation() throws RemoteException {
        String arg = getNextArg();
        if (arg == null) {
            getErrPrintWriter().println("Error: no install location specified.");
            return 1;
        }
        try {
            int loc = Integer.parseInt(arg);
            if (!this.mInterface.setInstallLocation(loc)) {
                getErrPrintWriter().println("Error: install location has to be a number.");
                return 1;
            }
            return 0;
        } catch (NumberFormatException e) {
            getErrPrintWriter().println("Error: install location has to be a number.");
            return 1;
        }
    }

    private int runGetInstallLocation() throws RemoteException {
        int loc = this.mInterface.getInstallLocation();
        String locStr = "invalid";
        if (loc == 0) {
            locStr = UiModeManagerService.Shell.NIGHT_MODE_STR_AUTO;
        } else if (loc == 1) {
            locStr = "internal";
        } else if (loc == 2) {
            locStr = "external";
        }
        getOutPrintWriter().println(loc + "[" + locStr + "]");
        return 0;
    }

    public int runMovePackage() throws RemoteException {
        String packageName = getNextArg();
        if (packageName == null) {
            getErrPrintWriter().println("Error: package name not specified");
            return 1;
        }
        String volumeUuid = getNextArg();
        if ("internal".equals(volumeUuid)) {
            volumeUuid = null;
        }
        int moveId = this.mInterface.movePackage(packageName, volumeUuid);
        int status = this.mInterface.getMoveStatus(moveId);
        while (!PackageManager.isMoveStatusFinished(status)) {
            SystemClock.sleep(1000L);
            status = this.mInterface.getMoveStatus(moveId);
        }
        if (status == -100) {
            getOutPrintWriter().println("Success");
            return 0;
        }
        getErrPrintWriter().println("Failure [" + status + "]");
        return 1;
    }

    public int runMovePrimaryStorage() throws RemoteException {
        String volumeUuid = getNextArg();
        if ("internal".equals(volumeUuid)) {
            volumeUuid = null;
        }
        int moveId = this.mInterface.movePrimaryStorage(volumeUuid);
        int status = this.mInterface.getMoveStatus(moveId);
        while (!PackageManager.isMoveStatusFinished(status)) {
            SystemClock.sleep(1000L);
            status = this.mInterface.getMoveStatus(moveId);
        }
        if (status == -100) {
            getOutPrintWriter().println("Success");
            return 0;
        }
        getErrPrintWriter().println("Failure [" + status + "]");
        return 1;
    }

工厂菜单

探索时,分析了一个包名,意外触发工厂菜单

# 留白一下,终端历史记录好像被我清空了

总之,触发后长这样:

01.webp
01.webp

TCL/雷鸟电视 adb 禁止安装 APK 研究
https://blog.mxdyeah.com/post/tcl-ban-via-adb-install-apk-analysis
本文作者 mxdyeah
发布时间 2026-07-10
许可协议 CC BY-NC-SA 4.0
发表新评论

AD: