SukiSU Ultra 源码里的逆天操作

25 年 6 月 30 日 星期一
2208 字
12 分钟

SukiSU Ultra 源码里的逆天操作

SukiSU Ultra,一个名字抽象、包名抽象、作者抽象、用户抽象的在GitHub上有超过2K Stars的KernelSU Fork抽象项目,其中的代码质量更是抽象到极致。

让人很难接受,居然是“Suki” “SU”,这分明就是要害了root,给KernelSU这个巧克力蛋糕上浇💩。

抽象源码大合集

SukiSU Ultra的包名已经很抽象了,com.sukisu.ultraio.sukisu.ultra以及zako.zako.zako,图标还是个表情包,看来写SukiSU Ultra的和用SukiSU Ultra的都是纯贬低意的”杂鱼“

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt

fun getKpmVersion(): String {
    val shell = getRootShell()
    val cmd = "${getKpmmgrPath()} version"
    val result = ShellUtils.fastCmd(shell, cmd)
    return result.trim()
}


// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/zako/zako/zako/zakoui/activity/util/AppData.kt

fun getKpmVersionUse(): String {
    return try {
        if (!rootAvailable()) return ""
        val version = getKpmVersion()
        if (version.isEmpty()) "" else version
    } catch (e: Exception) {
        "Error: ${e.message}"
    }
}

调用的时候反倒是这样的

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/zako/zako/zako/zakoui/activity/component/BottomBar.kt

val kpmVersion = getKpmVersionUse()
!kpmVersion.startsWith("Error")

就是说,为了判断是否正确,还去专门判断了startsWith("Error"),但是,有没有一种可能,这个调用过程中是不会有可能报错的?

并且,为什么不有报错就return ""呢?

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/zako/zako/zako/zakoui/flash/KernelFlash.kt

private fun runCommand(su: Boolean, cmd: String): Int {
    val process = ProcessBuilder(if (su) "su" else "sh")
        .redirectErrorStream(true)
        .start()

    return try {
        process.outputStream.bufferedWriter().use { writer ->
            writer.write("$cmd\n")
            writer.write("exit\n")
            writer.flush()
        }
        process.waitFor()
    } finally {
        process.destroy()
    }
}

private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
    val process = ProcessBuilder(if (su) "su" else "sh")
        .redirectErrorStream(true)
        .start()

    return try {
        process.outputStream.bufferedWriter().use { writer ->
            writer.write("$cmd\n")
            writer.write("exit\n")
            writer.flush()
        }
        process.inputStream.bufferedReader().use { reader ->
            reader.readText().trim()
        }
    } catch (_: Exception) {
        ""
    } finally {
        process.destroy()
    }
}

private fun rootAvailable(): Boolean {
    return try {
        val process = Runtime.getRuntime().exec("su -c id")
        val exitValue = process.waitFor()
        exitValue == 0
    } catch (_: Exception) {
        false
    }
}

这些代码纯纯就是闲的没事干,源码里有大量的ProcessBuilder()Runtime.getRuntime().exec()滥用,并且还用su -c id检测是否有root,你一个root管理器不应该在进去软件的时候就知道自己有没有root的吗?而且还用su -c id然后检测返回值?su -c true不行吗?

java
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/io/sukisu/ultra/UltraShellHelper.java

public static boolean isPathExists(String path) {
    return runCmd("file " + path).contains("No such file or directory");
}

public static void CopyFileTo(String path, String target) {
    runCmd("cp -f " + path + " " + target);
}

SukiSU Ultra在不断刷新我对低劣代码的认知,我是真的第一次遇到检测文件存在用file然后判断有没有报错No such file or directory的。

都是root管理器了,还在用shell来检测文件复制文件,还是通过这种莫名其妙的方法,很难想象是怎么写出来的,我怎么写也想不到用file来检测,顶多想到test -f[ -f ]、[[-f]]`这些东西。

java
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java

private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd";
public static void tryToInstall() {
    String kpmmgrPath = getKpmmgrPath();
    if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
        UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
        UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
    }
    String SuSFSDaemonPath = getSuSFSDaemonPath();
    if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
        UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
        UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
        }
}

硬编码+shell调用,没什么好说的。

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/com/sukisu/ultra/Natives.kt

init {
    System.loadLibrary("zako")
}

纯纯杂鱼。

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSModuleScripts.kt

private const val LOG_DIR = "/data/adb/ksu/log"

// 清理残留脚本生成
private fun StringBuilder.generateCleanupResidueSection() {
    appendLine("# 清理工具残留文件")
    appendLine("echo \"$(get_current_time): 开始清理工具残留\" >> \"${'$'}LOG_FILE\"")
    appendLine()

    // 定义清理函数
    appendLine("""
    cleanup_path() {
        local path="$1"
        local desc="$2"
        local current="$3"
        local total="$4"

        if [ -n "${'$'}desc" ]; then
            echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path (${'$'}desc)" >> "${'$'}LOG_FILE"
         else
            echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path" >> "${'$'}LOG_FILE"
        fi

        if rm -rf "${'$'}path" 2>/dev/null; then
            echo "$(get_current_time): ✓ 成功清理: ${'$'}path" >> "${'$'}LOG_FILE"
        else
            echo "$(get_current_time): ✗ 清理失败或不存在: ${'$'}path" >> "${'$'}LOG_FILE"
        fi
     }
""".trimIndent())

    appendLine()
    appendLine("# 开始清理各种工具残留")
    appendLine("TOTAL=33")
    appendLine()

    val cleanupPaths = listOf(
        "/data/local/stryker/" to "Stryker残留",
        "/data/system/AppRetention" to "AppRetention残留",
        "/data/local/tmp/luckys" to "Luck Tool残留",
        "/data/local/tmp/HyperCeiler" to "西米露残留",
        "/data/local/tmp/simpleHook" to "simple Hook残留",
        "/data/local/tmp/DisabledAllGoogleServices" to "谷歌省电模块残留",
        "/data/local/MIO" to "解包软件",
        "/data/DNA" to "解包软件",
        "/data/local/tmp/cleaner_starter" to "质感清理残留",
        "/data/local/tmp/byyang" to "",
        "/data/local/tmp/mount_mask" to "",
        "/data/local/tmp/mount_mark" to "",
        "/data/local/tmp/scriptTMP" to "",
        "/data/local/luckys" to "",
        "/data/local/tmp/horae_control.log" to "",
        "/data/gpu_freq_table.conf" to "",
        "/storage/emulated/0/Download/advanced/" to "",
        "/storage/emulated/0/Documents/advanced/" to "爱玩机",
        "/storage/emulated/0/Android/naki/" to "旧版asoulopt",
        "/data/swap_config.conf" to "scene附加模块2",
        "/data/local/tmp/resetprop" to "",
        "/dev/cpuset/AppOpt/" to "AppOpt模块",
        "/storage/emulated/0/Android/Clash/" to "Clash for Magisk模块",
        "/storage/emulated/0/Android/Yume-Yunyun/" to "网易云后台优化模块",
        "/data/local/tmp/Surfing_update" to "Surfing模块缓存",
        "/data/encore/custom_default_cpu_gov" to "encore模块",
        "/data/encore/default_cpu_gov" to "encore模块",
        "/data/local/tmp/yshell" to "",
        "/data/local/tmp/encore_logo.png" to "",
        "/storage/emulated/legacy/" to "",
        "/storage/emulated/elgg/" to "",
        "/data/system/junge/" to "",
        "/data/local/tmp/mount_namespace" to "挂载命名空间残留"
    )

    cleanupPaths.forEachIndexed { index, (path, desc) ->
        val current = index + 1
        appendLine("cleanup_path '$path' '$desc' $current \$TOTAL")
    }

    appendLine()
    appendLine("echo \"$(get_current_time): 工具残留清理完成\" >> \"${'$'}LOG_FILE\"")
    appendLine()
}

由于这一整个文件写的全都是垃圾,就只挑一部分说。

缩进嘛,就挺抽象的。

首当其冲的是路径硬编码,不用多说。

让人难以理解的是listOf()的大小也要硬编码,你但凡调用一下它的属性呢?

还写个残留清理功能,纯属没活硬整,连DNA都删上了,而且还删cgroup节点,完全就是没有任何Android内核相关常识。

让人最难以理解的是${'$'},看到这个东西的时候我脑子都要炸掉了,整个脑子里都是骂人的话,这个东西究竟是不是人类写出来的?我感觉连类人生物都算不上。

说他不好吧,他还知道用单引号包裹,只取Char类型。

但是呢,${'$'}这种写法到底是什么神人写法,实用性为0而且纯纯给代码凑字数,而且${'$'}\$混用,写这么多转义就不能用一下$$吗?

kotlin
// https://github.com/SukiSU-Ultra/SukiSU-Ultra/blob/main/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt

private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"

private fun getSuSFSTargetPath(): String = "/data/adb/ksu/bin/${getSuSFSBinaryName()}"

// 槽位信息获取
suspend fun getCurrentSlotInfo(): List<SlotInfo> = withContext(Dispatchers.IO) {
    try {
        val slotInfoList = mutableListOf<SlotInfo>()
        val shell = Shell.getShell()

        listOf("boot_a", "boot_b").forEach { slot ->
            val unameCmd =
                "strings -n 20 /dev/block/by-name/$slot | awk '/Linux version/ && ++c==2 {print $3; exit}'"
            val buildTimeCmd = "strings -n 20 /dev/block/by-name/$slot | sed -n '/Linux version.*#/{s/.*#/#/p;q}'"

            val uname = runCmd(shell, unameCmd).trim()
            val buildTime = runCmd(shell, buildTimeCmd).trim()

            if (uname.isNotEmpty() && buildTime.isNotEmpty()) {
                slotInfoList.add(SlotInfo(slot, uname.ifEmpty { "unknown" }, buildTime.ifEmpty { "unknown" }))
            }
        }

        slotInfoList
    } catch (e: Exception) {
        e.printStackTrace()
        emptyList()
    }
}

 /**
 * 模块管理
 */
private suspend fun updateMagiskModule(context: Context): Boolean {
    return removeMagiskModule() && createMagiskModule(context)
}

/**
 * 模块创建方法
 */
private suspend fun createMagiskModule(context: Context): Boolean = withContext(Dispatchers.IO) {
    try {
        val config = getCurrentModuleConfig(context)

        // 创建模块目录
        if (!runCmdWithResult("mkdir -p $MODULE_PATH").isSuccess) return@withContext false

        // 创建module.prop
        val moduleProp = ScriptGenerator.generateModuleProp(MODULE_ID)
        if (!runCmdWithResult("cat > $MODULE_PATH/module.prop << 'EOF'\n$moduleProp\nEOF").isSuccess) return@withContext false

        // 生成并创建所有脚本文件
        val scripts = ScriptGenerator.generateAllScripts(config)

        scripts.all { (filename, content) ->
            runCmdWithResult("cat > $MODULE_PATH/$filename << 'EOF'\n$content\nEOF").isSuccess &&
                    runCmdWithResult("chmod 755 $MODULE_PATH/$filename").isSuccess
        }
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

private suspend fun removeMagiskModule(): Boolean = withContext(Dispatchers.IO) {
    try {
        runCmdWithResult("rm -rf $MODULE_PATH").isSuccess
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

真就是演都不演了,在KernelSU里面整上Magisk了。

首当其冲的仍然是硬编码。

而且已经把kotlin当shell辅助工具玩了,干脆改名成ShellSU得了。

kotlin
suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
    withContext(Dispatchers.IO) {
        try {
            val busyboxPath = "/data/adb/ksu/bin/busybox"
            val moduleDir = "/data/adb/modules"

            // 直接将tar输出重定向到用户选择的文件
            val command = """
                cd "$moduleDir" &&
                $busyboxPath tar -cz ./* > /proc/self/fd/1
            """.trimIndent()

            val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))

            // 直接将tar输出写入到用户选择的文件
            context.contentResolver.openOutputStream(uri)?.use { output ->
                process.inputStream.copyTo(output)
            }

            val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
            if (process.exitValue() != 0) {
                throw IOException(context.getString(R.string.command_execution_failed, error))
            }

            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.backup_success),
                    duration = SnackbarDuration.Long
                )
            }

        } catch (e: Exception) {
            Log.e("Backup", context.getString(R.string.backup_failed, ""), e)
            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.backup_failed, e.message),
                    duration = SnackbarDuration.Long
                )
            }
        }
    }
}

suspend fun restoreModules(
    context: Context,
    snackBarHost: SnackbarHostState,
    uri: Uri,
    showConfirmDialog: (Boolean) -> Unit,
    confirmResult: CompletableDeferred<Boolean>
) {
    // 显示确认对话框
    withContext(Dispatchers.Main) {
        showConfirmDialog(true)
    }

    val userConfirmed = confirmResult.await()
    if (!userConfirmed) return

    withContext(Dispatchers.IO) {
        try {
            val busyboxPath = "/data/adb/ksu/bin/busybox"
            val moduleDir = "/data/adb/modules"

            // 直接从用户选择的文件读取并解压
            val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir"))

            context.contentResolver.openInputStream(uri)?.use { input ->
                input.copyTo(process.outputStream)
            }
            process.outputStream.close()

            process.waitFor()

            val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
            if (process.exitValue() != 0) {
                throw IOException(context.getString(R.string.command_execution_failed, error))
            }

            withContext(Dispatchers.Main) {
                val snackbarResult = snackBarHost.showSnackbar(
                    message = context.getString(R.string.restore_success),
                    actionLabel = context.getString(R.string.restart_now),
                    duration = SnackbarDuration.Long
                )
                if (snackbarResult == SnackbarResult.ActionPerformed) {
                    reboot()
                }
            }

        } catch (e: Exception) {
            Log.e("Restore", context.getString(R.string.restore_failed, ""), e)
            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    message = context.getString(
                        R.string.restore_failed,
                        e.message ?: context.getString(R.string.unknown_error)
                    ),
                    duration = SnackbarDuration.Long
                )
            }
        }
    }
}

suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
    withContext(Dispatchers.IO) {
        try {
            val allowlistPath = "/data/adb/ksu/.allowlist"

            // 直接复制文件到用户选择的位置
            val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat $allowlistPath"))

            context.contentResolver.openOutputStream(uri)?.use { output ->
                process.inputStream.copyTo(output)
            }

            val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
            if (process.exitValue() != 0) {
                throw IOException(context.getString(R.string.command_execution_failed, error))
            }

            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.allowlist_backup_success),
                    duration = SnackbarDuration.Long
                )
            }

        } catch (e: Exception) {
            Log.e("AllowlistBackup", context.getString(R.string.allowlist_backup_failed, ""), e)
            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.allowlist_backup_failed, e.message),
                    duration = SnackbarDuration.Long
                )
            }
        }
    }
}

suspend fun restoreAllowlist(
    context: Context,
    snackBarHost: SnackbarHostState,
    uri: Uri,
    showConfirmDialog: (Boolean) -> Unit,
    confirmResult: CompletableDeferred<Boolean>
) {
    // 显示确认对话框
    withContext(Dispatchers.Main) {
        showConfirmDialog(true)
    }

    val userConfirmed = confirmResult.await()
    if (!userConfirmed) return

    withContext(Dispatchers.IO) {
        try {
            val allowlistPath = "/data/adb/ksu/.allowlist"

            // 直接从用户选择的文件读取并写入到目标位置
            val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat > $allowlistPath"))

            context.contentResolver.openInputStream(uri)?.use { input ->
                input.copyTo(process.outputStream)
            }
            process.outputStream.close()

            process.waitFor()

            val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
            if (process.exitValue() != 0) {
                throw IOException(context.getString(R.string.command_execution_failed, error))
            }

            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.allowlist_restore_success),
                    duration = SnackbarDuration.Long
                )
            }

        } catch (e: Exception) {
            Log.e("AllowlistRestore", context.getString(R.string.allowlist_restore_failed, ""), e)
            withContext(Dispatchers.Main) {
                snackBarHost.showSnackbar(
                    context.getString(R.string.allowlist_restore_failed, e.message),
                    duration = SnackbarDuration.Long
                )
            }
        }
    }
}

private fun reboot() {
    Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot"))
}

真就是演都不演了,硬编码、Runtime.getRuntime().exec()滥用、su -c滥用、shell滥用轮着来。

要是说${'$'}是我见过的最抽象的kotlin写法,那么> /proc/self/fd/1就是我见过的最抽象的shell写法。

能想到> /proc/self/fd/1的已经离人非常远了,可能会想到&>1,但是要不想想默认输出走的是哪呢?

总结

SukiSU Ultra到处都充斥着各种抽象滥用行为,甚至README都能把大小写写错,这种抽象的项目真的有人能用得下去吗?

文章标题:SukiSU Ultra 源码里的逆天操作

文章作者:回忆溢出工作组

文章链接:https://oom-wg.dev/posts/go4-sukisu-ultra[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用File to Download Public Resources License进行许可。