mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-04-29 22:24:35 +02:00
refactor(core): security features
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
parent
62350a048c
commit
9761d73ece
@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import kotlinx.coroutines.launch
|
||||
@ -52,9 +53,9 @@ class HomeSettings : Routes.Route() {
|
||||
.clickable {
|
||||
value = !value
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putBoolean(realKey, value)
|
||||
.apply()
|
||||
.edit() {
|
||||
putBoolean(realKey, value)
|
||||
}
|
||||
},
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@ -284,7 +285,7 @@ class HomeSettings : Routes.Route() {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_sif_prod", text = "Disable Snap Integrity Fix")
|
||||
PreferenceToggle(context.sharedPreferences, key = "enable_security_features", text = "Enable Security Features")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ class ModContext(
|
||||
val isDeveloper by lazy { config.scripting.developerMode.get() }
|
||||
|
||||
var isMainActivityPaused = true
|
||||
var isSafeMode = false
|
||||
|
||||
fun <T : Feature> feature(featureClass: KClass<T>): T {
|
||||
return features.get(featureClass)!!
|
||||
|
@ -1,16 +1,20 @@
|
||||
package me.rhunk.snapenhance.core
|
||||
|
||||
import android.system.Os
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.NotInterested
|
||||
import androidx.compose.material.icons.rounded.NotInterested
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||
@ -18,13 +22,8 @@ import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.toWrapper
|
||||
import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK
|
||||
import me.rhunk.snapenhance.common.config.VersionRequirement
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.common.ui.createComposeView
|
||||
import me.rhunk.snapenhance.core.ui.CustomComposable
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import java.io.FileDescriptor
|
||||
import kotlin.text.isNotEmpty
|
||||
|
||||
class SecurityFeatures(
|
||||
private val context: ModContext
|
||||
@ -39,10 +38,9 @@ class SecurityFeatures(
|
||||
transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
|
||||
}
|
||||
|
||||
fun init(): Boolean {
|
||||
private fun isSafeMode(): Boolean {
|
||||
val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code")
|
||||
val shouldUseSafeMode = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED // true if version is >12.81.0.44
|
||||
val debugDisable = context.bridgeClient.getDebugProp("disable_sif_prod", "false") == "true"
|
||||
|
||||
context.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let {
|
||||
runCatching {
|
||||
@ -54,74 +52,28 @@ class SecurityFeatures(
|
||||
context.log.error("Failed to load custom shared library", it)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (!debugDisable) {
|
||||
runCatching {
|
||||
context.native.loadSharedLibrary(
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
|
||||
.toWrapper()
|
||||
.readBytes()
|
||||
.takeIf {
|
||||
it.isNotEmpty()
|
||||
} ?: throw IllegalStateException("buffer is empty")
|
||||
)
|
||||
context.log.verbose("loaded sif")
|
||||
}.onFailure {
|
||||
context.log.error("Failed to load sif", it)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
context.log.warn("sif is disabled")
|
||||
}
|
||||
} ?: context.bridgeClient.getDebugProp("enable_security_features", "false").takeIf { it == "true" }?.runCatching {
|
||||
context.native.loadSharedLibrary(
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
|
||||
.toWrapper()
|
||||
.readBytes()
|
||||
.takeIf {
|
||||
it.isNotEmpty()
|
||||
} ?: throw IllegalStateException("Binary is empty")
|
||||
)
|
||||
context.log.verbose("loaded sif")
|
||||
}?.onFailure {
|
||||
context.log.error("Failed to load sif: " + it.message)
|
||||
return shouldUseSafeMode
|
||||
} ?: context.log.warn("Security features are disabled")
|
||||
|
||||
token // pre init token
|
||||
|
||||
context.event.subscribe(UnaryCallEvent::class) { event ->
|
||||
if (!event.uri.contains("/Login")) return@subscribe
|
||||
|
||||
// intercept login response
|
||||
event.addResponseCallback {
|
||||
val response = ProtoReader(buffer)
|
||||
val isBlocked = when {
|
||||
event.uri.contains("TLv") -> response.getVarInt(1) == 14L
|
||||
else -> response.getVarInt(1) == 16L
|
||||
}
|
||||
|
||||
val errorDataIndex = when {
|
||||
response.contains(11) -> 11
|
||||
response.contains(10) -> 10
|
||||
response.contains(8) -> 8
|
||||
else -> return@addResponseCallback
|
||||
}
|
||||
|
||||
if (isBlocked) {
|
||||
val status = transact(token ?: return@addResponseCallback, 1)
|
||||
?.takeIf { it != 0 }
|
||||
?.let {
|
||||
val buffer = ByteArray(8192)
|
||||
val fd = FileDescriptor().apply {
|
||||
setObjectField("descriptor", it)
|
||||
}
|
||||
val read = Os.read(fd, buffer, 0, buffer.size)
|
||||
Os.close(fd)
|
||||
buffer.copyOfRange(0, read).decodeToString()
|
||||
} ?: return@addResponseCallback
|
||||
|
||||
buffer = ProtoEditor(buffer).apply {
|
||||
edit(errorDataIndex) {
|
||||
remove(1)
|
||||
addString(1, status)
|
||||
}
|
||||
}.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val status = getStatus()
|
||||
val safeMode = shouldUseSafeMode && (status == null || status < 2)
|
||||
|
||||
if (status != null && status >= 2) {
|
||||
context.log.verbose("status=$status")
|
||||
lateinit var composable: CustomComposable
|
||||
composable = {
|
||||
Row(
|
||||
@ -140,17 +92,49 @@ class SecurityFeatures(
|
||||
context.inAppOverlay.addCustomComposable(composable)
|
||||
}
|
||||
|
||||
if (safeMode && !debugDisable) {
|
||||
context.features.addActivityCreateListener {
|
||||
context.inAppOverlay.showStatusToast(
|
||||
icon = Icons.Filled.NotInterested,
|
||||
text = "SnapEnhance is not compatible with this version of Snapchat without SIF and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections or use test accounts.",
|
||||
durationMs = 10000,
|
||||
maxLines = 6
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return safeMode
|
||||
}
|
||||
|
||||
fun init() {
|
||||
context.isSafeMode = isSafeMode()
|
||||
context.log.verbose("isSafeMode=${context.isSafeMode}")
|
||||
if (!context.isSafeMode) return
|
||||
|
||||
context.features.addActivityCreateListener { activity ->
|
||||
if (!activity.javaClass.name.endsWith("LoginSignupActivity")) return@addActivityCreateListener
|
||||
|
||||
activity.findViewById<ViewGroup>(android.R.id.content).apply {
|
||||
visibility = ViewGroup.INVISIBLE
|
||||
|
||||
post {
|
||||
addView(createComposeView(activity) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center).padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(Icons.Rounded.NotInterested, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(110.dp))
|
||||
Spacer(Modifier.height(50.dp))
|
||||
Text(
|
||||
"SnapEnhance can't be used to login or signup because your Snapchat version isn't the recommended one (v${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"}). Please downgrade to continue using it.\n\nFor more details, join t.me/snapenhance_chat",
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
visibility = ViewGroup.VISIBLE
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import me.rhunk.snapenhance.common.action.EnumAction
|
||||
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.toWrapper
|
||||
import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK
|
||||
import me.rhunk.snapenhance.common.data.FriendStreaks
|
||||
import me.rhunk.snapenhance.common.data.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.common.data.MessagingGroupInfo
|
||||
@ -202,7 +203,6 @@ class SnapEnhance {
|
||||
it.isNotEmpty()
|
||||
}?.toString(Charsets.UTF_8)?.also {
|
||||
appContext.native.signatureCache = it
|
||||
appContext.log.verbose("old signature cache $it")
|
||||
}
|
||||
|
||||
val lateInit = appContext.native.initOnce {
|
||||
@ -223,7 +223,7 @@ class SnapEnhance {
|
||||
}
|
||||
}
|
||||
|
||||
val safeMode = SecurityFeatures(appContext).init()
|
||||
SecurityFeatures(appContext).init()
|
||||
|
||||
Runtime::class.java.findRestrictedMethod {
|
||||
it.name == "loadLibrary0" && it.parameterTypes.contentEquals(
|
||||
@ -231,11 +231,18 @@ class SnapEnhance {
|
||||
else arrayOf(ClassLoader::class.java, String::class.java)
|
||||
)
|
||||
}!!.apply {
|
||||
if (safeMode) {
|
||||
if (appContext.isSafeMode) {
|
||||
hook(HookStage.BEFORE) { param ->
|
||||
if (param.arg<String>(1) != "scplugin") return@hook
|
||||
param.setResult(null)
|
||||
appContext.log.warn("Can't load scplugin in safe mode")
|
||||
appContext.runOnUiThread {
|
||||
appContext.inAppOverlay.showStatusToast(
|
||||
Icons.Outlined.Cancel,
|
||||
"SnapEnhance is not compatible with this version of Snapchat and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections.",
|
||||
durationMs = 7000,
|
||||
maxLines = 6
|
||||
)
|
||||
}
|
||||
runCatching {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
}.onFailure {
|
||||
@ -249,7 +256,6 @@ class SnapEnhance {
|
||||
hook(HookStage.AFTER) { param ->
|
||||
if (param.arg<String>(1) != "client") return@hook
|
||||
unhook()
|
||||
appContext.log.verbose("libclient lateInit")
|
||||
lateInit()
|
||||
}.also { unhook = { it.unhook() } }
|
||||
}
|
||||
@ -311,7 +317,8 @@ class SnapEnhance {
|
||||
}
|
||||
|
||||
val friends = feedEntries.filter { it.conversationType == 0 }.mapNotNull {
|
||||
val friendUserId = it.friendUserId ?: it.participants?.filter { it != appContext.database.myUserId }?.firstOrNull() ?: return@mapNotNull null
|
||||
val friendUserId = it.friendUserId ?: it.participants?.firstOrNull { it != appContext.database.myUserId }
|
||||
?: return@mapNotNull null
|
||||
val friend = appContext.database.getFriendInfo(friendUserId) ?: return@mapNotNull null
|
||||
|
||||
MessagingFriendInfo(
|
||||
|
Loading…
x
Reference in New Issue
Block a user