refactor: security features

- remove remote shared library manager
- prevent plugin from loading in newer versions

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
rhunk
2025-07-05 16:40:36 +02:00
parent 56e434cb15
commit 5702420f89
13 changed files with 86 additions and 362 deletions

View File

@@ -1,123 +0,0 @@
package me.rhunk.snapenhance
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.net.toUri
import me.rhunk.snapenhance.common.BuildConfig
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
class RemoteSharedLibraryManager(
private val remoteSideContext: RemoteSideContext
) {
private val okHttpClient = OkHttpClient()
private fun getVersion(): String? {
return runCatching {
okHttpClient.newCall(
Request.Builder()
.url("${BuildConfig.SIF_ENDPOINT}/version")
.build()
).execute().use { response ->
if (!response.isSuccessful) {
return null
}
response.body.string()
}
}.getOrNull()
}
private fun downloadLatest(outputFile: File): Boolean {
val abi = Build.SUPPORTED_ABIS.firstOrNull() ?: return false
val request = Request.Builder()
.url("${BuildConfig.SIF_ENDPOINT}/$abi.so")
.build()
runCatching {
okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
return false
}
response.body.byteStream().use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
return true
}
}.onFailure {
remoteSideContext.log.error("Failed to download latest sif", it)
}
return false
}
@SuppressLint("ApplySharedPref")
fun init() {
val libraryFile = InternalFileHandleType.SIF.resolve(remoteSideContext.androidContext)
val currentVersion = remoteSideContext.sharedPreferences.getString("sif", null)?.trim()
if (currentVersion == null || currentVersion == "false") {
libraryFile.takeIf { it.exists() }?.delete()
remoteSideContext.log.info("sif can't be loaded due to user preference")
return
}
val latestVersion = getVersion()?.trim() ?: run {
throw Exception("Failed to get latest sif version")
}
if (currentVersion == latestVersion) {
remoteSideContext.log.info("sif is up to date ($currentVersion)")
return
}
remoteSideContext.log.info("Updating sif from $currentVersion to $latestVersion")
if (downloadLatest(libraryFile)) {
remoteSideContext.sharedPreferences.edit().putString("sif", latestVersion).commit()
remoteSideContext.shortToast("SIF updated to $latestVersion!")
if (currentVersion.isNotEmpty()) {
val notificationManager = remoteSideContext.androidContext.getSystemService(NotificationManager::class.java)
val channelId = "sif_update"
notificationManager.createNotificationChannel(
NotificationChannel(
channelId,
"SIF Updates",
NotificationManager.IMPORTANCE_DEFAULT
)
)
notificationManager.notify(
System.nanoTime().toInt(),
Notification.Builder(remoteSideContext.androidContext, channelId)
.setContentTitle("SnapEnhance")
.setContentText("Security Features have been updated to version $latestVersion")
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(PendingIntent.getActivity(
remoteSideContext.androidContext,
0,
Intent().apply {
action = Intent.ACTION_VIEW
data = "https://github.com/SnapEnhance/resources".toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)).build()
)
}
// force restart snapchat
runCatching {
remoteSideContext.config.configStateListener?.takeIf { it.asBinder().pingBinder() }?.onRestartRequired()
}
} else {
remoteSideContext.log.warn("Failed to download latest sif")
throw Exception("Failed to download latest sif")
}
}
}

View File

@@ -78,7 +78,6 @@ class RemoteSideContext(
val tracker = RemoteTracker(this)
val accountStorage = RemoteAccountStorage(this)
val locationManager = RemoteLocationManager(this)
val remoteSharedLibraryManager = RemoteSharedLibraryManager(this)
//used to load bitmoji selfies and download previews
val imageLoader by lazy {
@@ -132,13 +131,6 @@ class RemoteSideContext(
messageLogger.purgeTrackerLogs(it)
}
}
coroutineScope.launch {
runCatching {
remoteSharedLibraryManager.init()
}.onFailure {
log.error("Failed to init RemoteSharedLibraryManager", it)
}
}
}
}.onFailure {
log.error("Failed to load RemoteSideContext", it)
@@ -220,10 +212,6 @@ class RemoteSideContext(
requirements = requirements or Requirements.MAPPINGS
}
if (sharedPreferences.getString("sif", null) == null) {
requirements = requirements or Requirements.SIF
}
if (requirements == 0) return false
val currentContext = activity ?: androidContext

View File

@@ -154,9 +154,6 @@ class HomeSettings : Routes.Route() {
RowAction(key = "change_language") {
context.checkForRequirements(Requirements.LANGUAGE)
}
RowAction(key = "security_features") {
context.checkForRequirements(Requirements.SIF)
}
RowTitle(title = translation["message_logger_title"])
ShiftedRow {
Column(
@@ -285,7 +282,7 @@ class HomeSettings : Routes.Route() {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
PreferenceToggle(context.sharedPreferences, key = "enable_security_features", text = "Enable Security Features")
PreferenceToggle(context.sharedPreferences, key = "test_mode", text = "Test Mode (Debugging)")
PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading")
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
}

View File

@@ -29,7 +29,10 @@ import androidx.navigation.compose.rememberNavController
import me.rhunk.snapenhance.SharedContextHolder
import me.rhunk.snapenhance.common.ui.AppMaterialTheme
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
import me.rhunk.snapenhance.ui.setup.screens.impl.*
import me.rhunk.snapenhance.ui.setup.screens.impl.MappingsScreen
import me.rhunk.snapenhance.ui.setup.screens.impl.PermissionsScreen
import me.rhunk.snapenhance.ui.setup.screens.impl.PickLanguageScreen
import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen
class SetupActivity : ComponentActivity() {
@@ -66,9 +69,6 @@ class SetupActivity : ComponentActivity() {
if (isFirstRun || hasRequirement(Requirements.MAPPINGS)) {
add(MappingsScreen().apply { route = "mappings" })
}
if (isFirstRun || hasRequirement(Requirements.SIF)) {
add(SecurityScreen().apply { route = "security" })
}
}
// If there are no required screens, we can just finish the activity

View File

@@ -1,132 +0,0 @@
package me.rhunk.snapenhance.ui.setup.screens.impl
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.WarningAmber
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
class SecurityScreen : SetupScreen() {
@SuppressLint("ApplySharedPref")
@Composable
override fun Content() {
Icon(
imageVector = Icons.Default.WarningAmber,
contentDescription = null,
modifier = Modifier
.padding(16.dp)
.size(30.dp),
)
DialogText(
"Since Snapchat has implemented additional security measures against third-party applications such as SnapEnhance, we offer a non-opensource solution that reduces the risk of banning and prevents Snapchat from detecting SnapEnhance. " +
"\nPlease note that this solution does not provide a ban bypass or spoofer for anything, and does not take any personal data or communicate with the network." +
"\nWe also encourage you to use official signed builds to avoid compromising the security of your account." +
"\nIf you're having trouble using the solution, or are experiencing crashes, join the Telegram Group for help: https://t.me/snapenhance_chat"
)
var denyDialog by remember { mutableStateOf(false) }
if (denyDialog) {
AlertDialog(
onDismissRequest = {
denyDialog = false
},
text = {
Text("Are you sure you don't want to use this solution? You can always change this later in the settings in the SnapEnhance app.")
},
dismissButton = {
Button(onClick = {
denyDialog = false
}) {
Text("Go back")
}
},
confirmButton = {
Button(onClick = {
context.sharedPreferences.edit().putString("sif", "false").apply()
goNext()
}) {
Text("Yes, I'm sure")
}
}
)
}
var downloadJob by remember { mutableStateOf(null as Job?) }
var jobError by remember { mutableStateOf(null as Throwable?) }
if (downloadJob != null) {
AlertDialog(onDismissRequest = {
downloadJob?.cancel()
downloadJob = null
}, confirmButton = {}, text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
if (jobError != null) {
Text("Failed to download the required files.\n\n${jobError?.message}")
} else {
Text("Downloading the required files...")
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
}
}
})
}
fun newDownloadJob() {
downloadJob?.cancel()
downloadJob = context.coroutineScope.launch {
context.sharedPreferences.edit().putString("sif", "").commit()
runCatching {
context.remoteSharedLibraryManager.init()
}.onFailure {
jobError = it
context.log.error("Failed to download the required files", it)
}.onSuccess {
downloadJob = null
withContext(Dispatchers.Main) {
goNext()
}
}
}
}
Column (
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Button(
onClick = {
newDownloadJob()
}
) {
Text("Accept and continue", fontSize = 18.sp, fontWeight = FontWeight.Bold)
}
Button(
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
onClick = {
denyDialog = true
}
) {
Text("I don't want to use this solution")
}
}
}
}

View File

@@ -1,17 +0,0 @@
package me.rhunk.snapenhance.ui.setup.screens.impl
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
class WelcomeScreen : SetupScreen() {
@Composable
override fun Content() {
Text(text = "Welcome")
Button(onClick = { allowNext(true) }) {
Text(text = "Next")
}
}
}