manager: SuSFS: Add SUS_MAP feature configuration item

This commit is contained in:
ShirkNeko
2025-10-18 15:09:02 +08:00
parent 8db55f56a9
commit eb5fdbbf3f
7 changed files with 335 additions and 16 deletions

View File

@@ -26,20 +26,10 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.susfs.component.AddAppPathDialog
import com.sukisu.ultra.ui.susfs.component.AddKstatStaticallyDialog
import com.sukisu.ultra.ui.susfs.component.AddPathDialog
import com.sukisu.ultra.ui.susfs.component.AddTryUmountDialog
import com.sukisu.ultra.ui.susfs.component.ConfirmDialog
import com.sukisu.ultra.ui.susfs.component.EnabledFeaturesContent
import com.sukisu.ultra.ui.susfs.component.KstatConfigContent
import com.sukisu.ultra.ui.susfs.component.PathSettingsContent
import com.sukisu.ultra.ui.susfs.component.SusLoopPathsContent
import com.sukisu.ultra.ui.susfs.component.SusMountsContent
import com.sukisu.ultra.ui.susfs.component.SusPathsContent
import com.sukisu.ultra.ui.susfs.component.TryUmountContent
import com.sukisu.ultra.ui.susfs.component.*
import com.sukisu.ultra.ui.theme.CardConfig
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion1512
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion159
import com.sukisu.ultra.ui.util.getSuSFSVersion
@@ -56,6 +46,7 @@ enum class SuSFSTab(val displayNameRes: Int) {
BASIC_SETTINGS(R.string.susfs_tab_basic_settings),
SUS_PATHS(R.string.susfs_tab_sus_paths),
SUS_LOOP_PATHS(R.string.susfs_tab_sus_loop_paths),
SUS_MAPS(R.string.susfs_tab_sus_maps),
SUS_MOUNTS(R.string.susfs_tab_sus_mounts),
TRY_UMOUNT(R.string.susfs_tab_try_umount),
KSTAT_CONFIG(R.string.susfs_tab_kstat_config),
@@ -63,11 +54,12 @@ enum class SuSFSTab(val displayNameRes: Int) {
ENABLED_FEATURES(R.string.susfs_tab_enabled_features);
companion object {
fun getAllTabs(isSusVersion158: Boolean, isSusVersion159: Boolean): List<SuSFSTab> {
fun getAllTabs(isSusVersion158: Boolean, isSusVersion159: Boolean, isSusVersion1512: Boolean): List<SuSFSTab> {
return when {
isSusVersion159 -> entries.toList()
isSusVersion1512 -> entries.toList()
isSusVersion159 -> entries.filter { it != SUS_MOUNTS}
isSusVersion158 -> entries.filter { it != SUS_LOOP_PATHS }
else -> entries.filter { it != PATH_SETTINGS && it != SUS_LOOP_PATHS }
else -> entries.filter { it != PATH_SETTINGS && it != SUS_LOOP_PATHS && it != SUS_MOUNTS }
}
}
}
@@ -106,6 +98,7 @@ fun SuSFSConfigScreen(
// 路径管理相关状态
var susPaths by remember { mutableStateOf(emptySet<String>()) }
var susLoopPaths by remember { mutableStateOf(emptySet<String>()) }
var susMaps by remember { mutableStateOf(emptySet<String>()) }
var susMounts by remember { mutableStateOf(emptySet<String>()) }
var tryUmounts by remember { mutableStateOf(emptySet<String>()) }
var androidDataPath by remember { mutableStateOf("") }
@@ -130,6 +123,7 @@ fun SuSFSConfigScreen(
// 对话框状态
var showAddPathDialog by remember { mutableStateOf(false) }
var showAddLoopPathDialog by remember { mutableStateOf(false) }
var showAddSusMapDialog by remember { mutableStateOf(false) }
var showAddAppPathDialog by remember { mutableStateOf(false) }
var showAddMountDialog by remember { mutableStateOf(false) }
var showAddUmountDialog by remember { mutableStateOf(false) }
@@ -139,6 +133,7 @@ fun SuSFSConfigScreen(
// 编辑状态
var editingPath by remember { mutableStateOf<String?>(null) }
var editingLoopPath by remember { mutableStateOf<String?>(null) }
var editingSusMap by remember { mutableStateOf<String?>(null) }
var editingMount by remember { mutableStateOf<String?>(null) }
var editingUmount by remember { mutableStateOf<String?>(null) }
var editingKstatConfig by remember { mutableStateOf<String?>(null) }
@@ -147,6 +142,7 @@ fun SuSFSConfigScreen(
// 重置确认对话框状态
var showResetPathsDialog by remember { mutableStateOf(false) }
var showResetLoopPathsDialog by remember { mutableStateOf(false) }
var showResetSusMapsDialog by remember { mutableStateOf(false) }
var showResetMountsDialog by remember { mutableStateOf(false) }
var showResetUmountsDialog by remember { mutableStateOf(false) }
var showResetKstatDialog by remember { mutableStateOf(false) }
@@ -160,7 +156,7 @@ fun SuSFSConfigScreen(
var isNavigating by remember { mutableStateOf(false) }
val allTabs = SuSFSTab.getAllTabs(isSusVersion158(), isSusVersion159())
val allTabs = SuSFSTab.getAllTabs(isSusVersion158(), isSusVersion159(), isSusVersion1512())
// 实时判断是否可以启用开机自启动
val canEnableAutoStart by remember {
@@ -307,6 +303,7 @@ fun SuSFSConfigScreen(
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
susPaths = SuSFSManager.getSusPaths(context)
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
susMaps = SuSFSManager.getSusMaps(context)
susMounts = SuSFSManager.getSusMounts(context)
tryUmounts = SuSFSManager.getTryUmounts(context)
androidDataPath = SuSFSManager.getAndroidDataPath(context)
@@ -479,6 +476,7 @@ fun SuSFSConfigScreen(
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
susPaths = SuSFSManager.getSusPaths(context)
susLoopPaths = SuSFSManager.getSusLoopPaths(context)
susMaps = SuSFSManager.getSusMaps(context)
susMounts = SuSFSManager.getSusMounts(context)
tryUmounts = SuSFSManager.getTryUmounts(context)
androidDataPath = SuSFSManager.getAndroidDataPath(context)
@@ -597,6 +595,35 @@ fun SuSFSConfigScreen(
initialValue = editingLoopPath ?: ""
)
AddPathDialog(
showDialog = showAddSusMapDialog,
onDismiss = {
showAddSusMapDialog = false
editingSusMap = null
},
onConfirm = { path ->
coroutineScope.launch {
isLoading = true
val success = if (editingSusMap != null) {
SuSFSManager.editSusMap(context, editingSusMap!!, path)
} else {
SuSFSManager.addSusMap(context, path)
}
if (success) {
susMaps = SuSFSManager.getSusMaps(context)
}
isLoading = false
showAddSusMapDialog = false
editingSusMap = null
}
},
isLoading = isLoading,
titleRes = if (editingSusMap != null) R.string.susfs_edit_sus_map else R.string.susfs_add_sus_map,
labelRes = R.string.susfs_sus_map_label,
placeholderRes = R.string.susfs_sus_map_placeholder,
initialValue = editingSusMap ?: ""
)
AddAppPathDialog(
showDialog = showAddAppPathDialog,
onDismiss = { showAddAppPathDialog = false },
@@ -817,6 +844,27 @@ fun SuSFSConfigScreen(
isDestructive = true
)
ConfirmDialog(
showDialog = showResetSusMapsDialog,
onDismiss = { showResetSusMapsDialog = false },
onConfirm = {
coroutineScope.launch {
isLoading = true
SuSFSManager.saveSusMaps(context, emptySet())
susMaps = emptySet()
if (SuSFSManager.isAutoStartEnabled(context)) {
SuSFSManager.configureAutoStart(context, true)
}
isLoading = false
showResetSusMapsDialog = false
}
},
titleRes = R.string.susfs_reset_sus_maps_title,
messageRes = R.string.susfs_reset_sus_maps_message,
isLoading = isLoading,
isDestructive = true
)
ConfirmDialog(
showDialog = showResetMountsDialog,
onDismiss = { showResetMountsDialog = false },
@@ -1036,6 +1084,28 @@ fun SuSFSConfigScreen(
}
}
SuSFSTab.SUS_MAPS -> {
OutlinedButton(
onClick = { showResetSusMapsDialog = true },
enabled = !isLoading && susMaps.isNotEmpty(),
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
) {
Icon(
imageVector = Icons.Default.RestoreFromTrash,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
stringResource(R.string.susfs_reset_sus_maps_title),
fontWeight = FontWeight.Medium
)
}
}
SuSFSTab.SUS_MOUNTS -> {
OutlinedButton(
onClick = { showResetMountsDialog = true },
@@ -1300,6 +1370,26 @@ fun SuSFSConfigScreen(
}
)
}
SuSFSTab.SUS_MAPS -> {
SusMapsContent(
susMaps = susMaps,
isLoading = isLoading,
onAddSusMap = { showAddSusMapDialog = true },
onRemoveSusMap = { map ->
coroutineScope.launch {
isLoading = true
if (SuSFSManager.removeSusMap(context, map)) {
susMaps = SuSFSManager.getSusMaps(context)
}
isLoading = false
}
},
onEditSusMap = { map ->
editingSusMap = map
showAddSusMapDialog = true
}
)
}
SuSFSTab.SUS_MOUNTS -> {
val isSusVersion158 = remember { isSusVersion158() }

View File

@@ -310,6 +310,116 @@ fun SusLoopPathsContent(
}
}
/**
* SUS Maps内容组件
*/
@Composable
fun SusMapsContent(
susMaps: Set<String>,
isLoading: Boolean,
onAddSusMap: () -> Unit,
onRemoveSusMap: (String) -> Unit,
onEditSusMap: ((String) -> Unit)? = null
) {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// 说明卡片
item {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.sus_maps_description_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
Text(
text = stringResource(R.string.sus_maps_description_text),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = stringResource(R.string.sus_maps_warning),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary
)
Text(
text = stringResource(R.string.sus_maps_debug_info),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
)
}
}
}
if (susMaps.isEmpty()) {
item {
EmptyStateCard(
message = stringResource(R.string.susfs_no_sus_maps_configured)
)
}
} else {
item {
SectionHeader(
title = stringResource(R.string.sus_maps_section),
subtitle = null,
icon = Icons.Default.Security,
count = susMaps.size
)
}
items(susMaps.toList()) { map ->
PathItemCard(
path = map,
icon = Icons.Default.Security,
onDelete = { onRemoveSusMap(map) },
onEdit = if (onEditSusMap != null) { { onEditSusMap(map) } } else null,
isLoading = isLoading
)
}
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Button(
onClick = onAddSusMap,
modifier = Modifier
.weight(1f)
.height(48.dp),
shape = RoundedCornerShape(8.dp)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(R.string.add))
}
}
}
}
}
}
/**
* SUS挂载内容组件
*/

View File

@@ -42,6 +42,8 @@ object SuSFSManager {
private const val KEY_AUTO_START_ENABLED = "auto_start_enabled"
private const val KEY_SUS_PATHS = "sus_paths"
private const val KEY_SUS_LOOP_PATHS = "sus_loop_paths"
private const val KEY_SUS_MAPS = "sus_maps"
private const val KEY_SUS_MOUNTS = "sus_mounts"
private const val KEY_TRY_UMOUNTS = "try_umounts"
private const val KEY_ANDROID_DATA_PATH = "android_data_path"
@@ -65,6 +67,7 @@ object SuSFSManager {
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
private const val MIN_VERSION_FOR_LOOP_PATH = "1.5.9"
private const val MIN_VERSION_SUS_MAPS = "1.5.12"
const val MAX_SUSFS_VERSION = "1.5.12"
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
private const val MEDIA_DATA_PATH = "/data/media/0/Android/data"
@@ -153,6 +156,7 @@ object SuSFSManager {
val executeInPostFsData: Boolean,
val susPaths: Set<String>,
val susLoopPaths: Set<String>,
val susMaps: Set<String>,
val susMounts: Set<String>,
val tryUmounts: Set<String>,
val androidDataPath: String,
@@ -175,6 +179,7 @@ object SuSFSManager {
buildTimeValue != DEFAULT_BUILD_TIME ||
susPaths.isNotEmpty() ||
susLoopPaths.isNotEmpty() ||
susMaps.isNotEmpty() ||
susMounts.isNotEmpty() ||
tryUmounts.isNotEmpty() ||
kstatConfigs.isNotEmpty() ||
@@ -264,6 +269,15 @@ object SuSFSManager {
}
}
fun isSusVersion1512(): Boolean {
return try {
val currentVersion = getSuSFSVersion()
compareVersions(currentVersion, MIN_VERSION_SUS_MAPS) >= 0
} catch (_: Exception) {
true
}
}
/**
* 获取当前模块配置
*/
@@ -275,6 +289,7 @@ object SuSFSManager {
executeInPostFsData = getExecuteInPostFsData(context),
susPaths = getSusPaths(context),
susLoopPaths = getSusLoopPaths(context),
susMaps = getSusMaps(context),
susMounts = getSusMounts(context),
tryUmounts = getTryUmounts(context),
androidDataPath = getAndroidDataPath(context),
@@ -379,6 +394,12 @@ object SuSFSManager {
fun getSusLoopPaths(context: Context): Set<String> =
getPrefs(context).getStringSet(KEY_SUS_LOOP_PATHS, emptySet()) ?: emptySet()
fun saveSusMaps(context: Context, maps: Set<String>) =
getPrefs(context).edit { putStringSet(KEY_SUS_MAPS, maps) }
fun getSusMaps(context: Context): Set<String> =
getPrefs(context).getStringSet(KEY_SUS_MAPS, emptySet()) ?: emptySet()
fun saveSusMounts(context: Context, mounts: Set<String>) =
getPrefs(context).edit { putStringSet(KEY_SUS_MOUNTS, mounts) }
@@ -544,6 +565,7 @@ object SuSFSManager {
KEY_AUTO_START_ENABLED to isAutoStartEnabled(context),
KEY_SUS_PATHS to getSusPaths(context),
KEY_SUS_LOOP_PATHS to getSusLoopPaths(context),
KEY_SUS_MAPS to getSusMaps(context),
KEY_SUS_MOUNTS to getSusMounts(context),
KEY_TRY_UMOUNTS to getTryUmounts(context),
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
@@ -1137,6 +1159,54 @@ object SuSFSManager {
}
}
// 添加 SUS Maps
suspend fun addSusMap(context: Context, map: String): Boolean {
val success = executeSusfsCommand(context, "add_sus_map '$map'")
if (success) {
saveSusMaps(context, getSusMaps(context) + map)
if (isAutoStartEnabled(context)) updateMagiskModule(context)
showToast(context, context.getString(R.string.susfs_sus_map_added_success, map))
}
return success
}
suspend fun removeSusMap(context: Context, map: String): Boolean {
saveSusMaps(context, getSusMaps(context) - map)
if (isAutoStartEnabled(context)) updateMagiskModule(context)
showToast(context, context.getString(R.string.susfs_sus_map_removed, map))
return true
}
suspend fun editSusMap(context: Context, oldMap: String, newMap: String): Boolean {
return try {
val currentMaps = getSusMaps(context).toMutableSet()
if (!currentMaps.remove(oldMap)) {
showToast(context, "Original SUS map not found: $oldMap")
return false
}
saveSusMaps(context, currentMaps)
val success = addSusMap(context, newMap)
if (success) {
showToast(context, context.getString(R.string.susfs_sus_map_updated, oldMap, newMap))
return true
} else {
// 如果添加新映射失败,恢复旧映射
currentMaps.add(oldMap)
saveSusMaps(context, currentMaps)
if (isAutoStartEnabled(context)) updateMagiskModule(context)
showToast(context, "Failed to update SUS map, reverted to original")
return false
}
} catch (e: Exception) {
e.printStackTrace()
showToast(context, "Error updating SUS map: ${e.message}")
false
}
}
// 添加SUS挂载
suspend fun addSusMount(context: Context, mount: String): Boolean {
val success = executeSusfsCommand(context, "add_sus_mount '$mount'")

View File

@@ -497,6 +497,10 @@ object ScriptGenerator {
if (config.susLoopPaths.isNotEmpty()) {
generateSusLoopPathsSection(config.susLoopPaths)
}
if (config.susMaps.isNotEmpty()) {
generateSusMapsSection(config.susMaps)
}
}
}
@@ -504,6 +508,17 @@ object ScriptGenerator {
}
}
private fun StringBuilder.generateSusMapsSection(susMaps: Set<String>) {
if (susMaps.isNotEmpty()) {
appendLine("# 添加SUS映射")
susMaps.forEach { map ->
appendLine("\"${'$'}SUSFS_BIN\" add_sus_map '$map'")
appendLine("echo \"$(get_current_time): 添加SUS映射: $map\" >> \"${'$'}LOG_FILE\"")
}
appendLine()
}
}
@SuppressLint("SdCardPath")
private fun StringBuilder.generatePathSettingSection(androidDataPath: String, sdcardPath: String) {
appendLine("# 路径配置")

View File

@@ -670,4 +670,21 @@
<string name="author">作者</string>
<string name="description">描述</string>
<string name="supported_devices">支持设备</string>
<!-- SUS Map related strings -->
<string name="susfs_tab_sus_maps">SUS映射</string>
<string name="susfs_sus_map_label">库文件路径</string>
<string name="susfs_sus_map_placeholder">/data/adb/modules/my_module/zygisk/arm64-v8a.so</string>
<string name="susfs_add_sus_map">添加SUS映射</string>
<string name="susfs_edit_sus_map">编辑SUS映射</string>
<string name="susfs_sus_map_added_success">SUS映射添加成功: %1$s</string>
<string name="susfs_sus_map_removed">SUS映射已移除: %1$s</string>
<string name="susfs_sus_map_updated">SUS映射已更新: %1$s -> %2$s</string>
<string name="susfs_no_sus_maps_configured">未配置SUS映射</string>
<string name="susfs_reset_sus_maps_title">重置SUS映射</string>
<string name="susfs_reset_sus_maps_message">这将移除所有已配置的SUS映射。此操作无法撤销。</string>
<string name="sus_maps_section">内存映射隐藏</string>
<string name="sus_maps_description_title">隐藏/proc/self/中各种映射中的mmap真实文件</string>
<string name="sus_maps_description_text">从 /proc/self/[maps|smaps|smaps_rollup|map_files|mem|pagemap] 中隐藏内存映射的真实文件路径。请注意:此功能不支持隐藏匿名内存映射,也无法隐藏由注入库本身产生的内联钩子或 PLT 钩子。</string>
<string name="sus_maps_warning">重要提示:对于具备完善注入检测机制的应用,此功能可能无法有效绕过检测。</string>
<string name="sus_maps_debug_info">首先通过 ps -enf 查找目标应用的 PID 和 UID然后检查 /proc/&lt;pid&gt;/maps 中的相关路径,并与 /proc/1/mountinfo 中的设备号进行比对以确保一致性。只有当设备号一致时,隐藏映射才能正常工作。</string>
</resources>

View File

@@ -678,4 +678,21 @@ Important Note:\n
<string name="author">Author</string>
<string name="description">Description</string>
<string name="supported_devices">Supported Devices</string>
<!-- SUS Map related strings -->
<string name="susfs_tab_sus_maps">SUS Maps</string>
<string name="susfs_sus_map_label">Library Path</string>
<string name="susfs_sus_map_placeholder">/data/adb/modules/my_module/zygisk/arm64-v8a.so</string>
<string name="susfs_add_sus_map">Add SUS Map</string>
<string name="susfs_edit_sus_map">Edit SUS Map</string>
<string name="susfs_sus_map_added_success">SUS map added successfully: %1$s</string>
<string name="susfs_sus_map_removed">SUS map removed: %1$s</string>
<string name="susfs_sus_map_updated">SUS map updated: %1$s -> %2$s</string>
<string name="susfs_no_sus_maps_configured">No SUS maps configured</string>
<string name="susfs_reset_sus_maps_title">Reset SUS Maps</string>
<string name="susfs_reset_sus_maps_message">This will remove all configured SUS maps. This action cannot be undone.</string>
<string name="sus_maps_section">Memory Map Hiding</string>
<string name="sus_maps_description_title">Hide the mmapped real file from various maps in /proc/self/</string>
<string name="sus_maps_description_text">Hide the real file paths of memory mappings from /proc/self/[maps|smaps|smaps_rollup|map_files|mem|pagemap]. Please note: This feature does not support hiding anonymous memory mappings, nor can it hide inline hooks or PLT hooks caused by the injected library itself.</string>
<string name="sus_maps_warning">Important Notice: For applications with well-implemented injection detection mechanisms, this feature may not effectively bypass detection.</string>
<string name="sus_maps_debug_info">First, find the target application\'s PID and UID using ps -enf, then check the relevant paths in /proc/&lt;pid&gt;/maps and compare the device numbers with those in /proc/1/mountinfo to ensure consistency. Only when the device numbers match can the map hiding function work properly.</string>
</resources>