使用 root 安装 APK,处理“/data/local/tmp/”文件夹的新限制

Posted

技术标签:

【中文标题】使用 root 安装 APK,处理“/data/local/tmp/”文件夹的新限制【英文标题】:Install APK using root, handling new limitations of "/data/local/tmp/" folder 【发布时间】:2018-11-05 12:15:11 【问题描述】:

背景

到目前为止,我可以通过以下代码使用 root(在应用程序内)安装 APK 文件:

pm install -t -f fullPathToApkFile

如果我想(尝试)安装到 sd 卡:

pm install -t -s fullPathToApkFile

问题

最近,不确定来自哪个 android 版本(至少在 Android P beta 上存在问题),上述方法失败,向我显示此消息:

avc:  denied   read  for  scontext=u:r:system_server:s0 tcontext=u:object_r:sdcardfs:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:sdcardfs:s0 (from path /storage/emulated/0/Download/FDroid.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/FDroid.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
Exception occurred while executing:
java.lang.IllegalArgumentException: Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
    at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:306)
    at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:884)
    at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:138)
    at android.os.ShellCommand.exec(ShellCommand.java:103)
    at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21125)
    at android.os.Binder.shellCommand(Binder.java:634)
    at android.os.Binder.onTransact(Binder.java:532)
    at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2806)
    at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3841)
    at android.os.Binder.execTransact(Binder.java:731)

这似乎也影响了热门应用,例如“Titanium backup (pro)”,无法恢复应用。

我尝试过的

查看所写的内容,它似乎没有安装/data/local/tmp/ 中不存在的APK 文件的权限。

所以我尝试了接下来的事情,看看我是否可以克服它:

    设置对文件的访问权限 (chmod 777) - 没有帮助。 向我的应用授予存储权限和REQUEST_INSTALL_PACKAGES(使用ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent)权限 - 没有帮助。

    使用官方 API 创建文件的符号链接,使其位于 /data/local/tmp/ 内:

     Os.symlink(fullPathToApkFile, symLinkFilePath)
    

    这没有做任何事情。

    使用此创建符号链接:

     ln -sf $fullPathToApkFile $symLinkFilePath
    

    这部分工作。该文件在那里,我可以在 Total Commander 应用程序中看到它,但是当我尝试检查它是否存在时,并且当我尝试从那里安装 APK 时,它失败了。

    将文件复制/移动(使用cpmv)到/data/local/tmp/ 路径,然后从那里安装。这行得通,但它有缺点:移动是有风险的,因为它会暂时隐藏原始文件,并且会更改原始文件的时间戳。复制很糟糕,因为仅用于安装(即使是临时的)占用了额外的空间,而且这样做会浪费时间。

    复制APK文件,告诉它避免实际复制(意味着硬链接),使用这个命令(取自here):

     cp -p -r -l $fullPathToApkFile $tempFileParentPath"
    

    这不起作用。它给了我这个错误:

     cp: /data/local/tmp/test.apk: Cross-device link
    

    检查在安装应用程序的其他情况下会发生什么。当您通过 IDE 安装时,它实际上会在此特殊路径中创建 APK 文件,但如果您通过 Play Store、简单 APK 安装(通过 Intent)或 adb(通过 PC)安装,则不会。

    在这里也写过这个:https://issuetracker.google.com/issues/80270303

问题

    有没有办法克服在这个特殊路径上使用 root 安装 APK 的缺点?甚至可以完全避免处理这条路径?

    为什么操作系统突然需要使用这个路径?为什么不使用原始路径,就像安装应用程序的其他方法一样?安装应用程序的其他方法有什么作用,以某种方式避免使用空间路径?

【问题讨论】:

【参考方案1】:

如果您不介意移动过程,一个解决方案是同时保存和恢复原始文件的时间戳,如下所示:

    val tempFileParentPath = "/data/local/tmp/"
    val tempFilePath = tempFileParentPath + File(fullPathToApkFile).name
    val apkTimestampTempFile = File(context.cacheDir, "apkTimestamp")
    apkTimestampTempFile.delete()
    apkTimestampTempFile.mkdirs()
    apkTimestampTempFile.createNewFile()
    root.runCommands("touch -r $fullPathToApkFile $apkTimestampTempFile.absolutePath")
    root.runCommands("mv $fullPathToApkFile $tempFileParentPath")
    root.runCommands("pm install -t -f $tempFilePath")
    root.runCommands("mv $tempFilePath $fullPathToApkFile")
    root.runCommands("touch -r $apkTimestampTempFile.absolutePath $fullPathToApkFile")
    apkTimestampTempFile.delete()

还是有点危险,不过比复制文件好……


编辑:谷歌向我展示了一个很好的解决方法(here):

我们不支持从设备上的随机目录安装 APK。它们要么需要使用“adb install”直接从主机安装,要么您必须流式传输内容才能安装--

$ cat foo.apk | pm install -S APK_SIZE

虽然我认为他们不支持从随机路径安装 APK 文件(以前一直有效)是不正确的,但解决方法似乎确实有效。我需要在安装 APK 文件的代码中进行更改:

val length = File(fullPathToApkFile ).length()
commands.add("cat $fullPathToApkFile | pm install -S $length")

问题是,现在我还有一些其他问题:

    此解决方法是否可以避免将 APK 移动/复制到存储中,并且不会影响原始文件? - 好像是的 这是否支持任何 APK 文件,甚至是大文件? - 对于占用 433MB 的 APK,它似乎成功了,所以我认为它可以安全地用于所有大小。 只有 Android P 才需要,对吧? - 到目前为止似乎如此。 为什么需要文件大小作为参数? - 不知道,但如果我删除它,它将无法工作

【讨论】:

我知道这是旧的但是你如何执行这个命令。我尝试将其传递为Runtime.getRuntime().exec(commands.toArray()) 我的应用是具有系统权限的系统应用,但运行此命令时出现错误提示“无法运行此程序” 如果您可以在这里查看我的问题,那将非常有帮助:- ***.com/questions/59089567/… @noname 抱歉,我不知道系统应用程序是如何做到的。其实我一直不明白如何在Android Q上转换成系统应用。你是怎么做到的?【参考方案2】:

感谢您的回答!我还在其他地方寻找了完整的 OTA 设置以适用于 Android 10 等等。它 100% 可在运行 Android 10 的三星 Galaxy Tab 10.1 上运行。

这是一篇带有代码的中型文章: https://medium.com/@jnishu1996/over-the-air-ota-updates-for-android-apps-download-apk-silent-apk-installation-auto-launch-8ee6f342197c

神奇之处在于以 root 访问权限运行此命令:

            process = Runtime.getRuntime().exec("su");
            out = process.getOutputStream();
            DataOutputStream dataOutputStream = new DataOutputStream(out);
            // Get all file permissions
            dataOutputStream.writeBytes("chmod 777 " + file.getPath() + "\n");
            // Perform silent installation command, all flags are necessary for some reason, only this works reliably post Android 10
            String installCommand = "cat " + file.getAbsolutePath() + "| pm install -d -t -S " + file.length();
            // Data to send to the LaunchActivity to the app knows it got updated and performs necessary functions to notify backend
            // es stands for extraString
            // In LaunchActivity onCreate(), you can get this data by running -> if (getIntent().getStringExtra("OTA").equals("true"))
            String launchCommandIntentArguments = "--es OTA true --es messageId " + MyApplication.mLastSQSMessage.receiptHandle();
            // Start a background thread to wait for 8 seconds before reopening the app's LaunchActivity, and pass necessary arguments
            String launchCommand = "(sleep 8; am start -n co.getpresso.Presso/.activities.LaunchActivity " + launchCommandIntentArguments + ")&";

            // The entire command is deployed with a ";" in the middle to launchCommand run after installCommand
            String installAndLaunchCommand = installCommand + "; " + launchCommand;

            // begins the installation
            dataOutputStream.writeBytes(installAndLaunchCommand);
            dataOutputStream.flush();
            // Close the stream operation
            dataOutputStream.close();
            out.close();
            int value = process.waitFor();

【讨论】:

以上是关于使用 root 安装 APK,处理“/data/local/tmp/”文件夹的新限制的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向获取安装在手机中的应用的 APK 包 ( 进入 adb shell | 获取 root 权限 | 进入 /data/app/ 目录 | 拷贝 base.apk 到外置存储 )(代码

想把E盘的apk文件用adb批处理安装到手机 system/app下怎么实现

Android 无需root实现apk的静默安装

如何在没有 root 访问权限的情况下获取已安装应用程序的 APK?

使用shell脚本实现批处理安装apk

Android 无需root实现apk的静默安装