commit 8e8220a55ea251752364d8e0add8c29f27c12e9b
parent 23666e55283a5b1f76f2533dff78d0154464707e
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Thu, 2 Jan 2025 19:18:31 +0100
refactor: security features
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
Diffstat:
5 files changed, 133 insertions(+), 118 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt
@@ -284,10 +284,9 @@ 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 = "disable_feature_loading", text = "Disable Feature Loading")
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
- PreferenceToggle(context.sharedPreferences, key = "disable_sif", text = "Disable Security Features")
- PreferenceToggle(context.sharedPreferences, key = "disable_mod_detection_version_check", text = "Disable Mod Detection Version Check")
}
}
Spacer(modifier = Modifier.height(50.dp))
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt
@@ -0,0 +1,126 @@
+package me.rhunk.snapenhance.core
+
+import android.system.Os
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.NotInterested
+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.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.core.util.ktx.setObjectField
+import java.io.FileDescriptor
+import kotlin.text.isNotEmpty
+
+class SecurityFeatures(
+ private val context: ModContext
+) {
+ private fun transact(option: Int, option2: Long) = runCatching { Os.prctl(option, option2, 0, 0, 0) }.getOrNull()
+
+ private val token by lazy {
+ transact(0, 0)
+ }
+
+ private fun getStatus() = token?.run {
+ transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
+ }
+
+ fun init(): Boolean {
+ val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code")
+ val mustUseSafeMode = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED
+ val debugDisable = context.bridgeClient.getDebugProp("disable_sif_prod", "false") != "true"
+
+ context.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let {
+ runCatching {
+ context.native.loadSharedLibrary(
+ context.fileHandlerManager.getFileHandle(FileHandleScope.USER_IMPORT.key, it).toWrapper().readBytes()
+ )
+ context.log.verbose("loaded custom shared library")
+ }.onFailure {
+ context.log.error("Failed to load custom shared library", it)
+ return true
+ }
+ }
+
+ if (debugDisable && !mustUseSafeMode) {
+ 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")
+ }
+
+ 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 = mustUseSafeMode || (status == null || status < 2)
+
+ 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
+ }
+}+
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt
@@ -223,35 +223,7 @@ class SnapEnhance {
}
}
- appContext.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let {
- runCatching {
- appContext.native.loadSharedLibrary(
- appContext.fileHandlerManager.getFileHandle(FileHandleScope.USER_IMPORT.key, it).toWrapper().readBytes()
- )
- appContext.log.verbose("loaded custom shared library")
- }.onFailure {
- appContext.log.error("Failed to load custom shared library", it)
- }
- }
-
- if (appContext.bridgeClient.getDebugProp("disable_sif", "false") != "true") {
- runCatching {
- appContext.native.loadSharedLibrary(
- appContext.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
- .toWrapper()
- .readBytes()
- .takeIf {
- it.isNotEmpty()
- } ?: throw IllegalStateException("buffer is empty")
- )
- appContext.log.verbose("loaded sif")
- }.onFailure {
- safeMode = true
- appContext.log.error("Failed to load sif", it)
- }
- } else {
- appContext.log.warn("sif is disabled")
- }
+ val safeMode = SecurityFeatures(appContext).init()
Runtime::class.java.findRestrictedMethod {
it.name == "loadLibrary0" && it.parameterTypes.contentEquals(
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt
@@ -28,6 +28,10 @@ class FeatureManager(
private val features = mutableMapOf<KClass<out Feature>, Feature>()
private val onActivityCreateListeners = mutableListOf<(Activity) -> Unit>()
+ fun addActivityCreateListener(block: (Activity) -> Unit) {
+ onActivityCreateListeners.add(block)
+ }
+
private fun register(vararg featureList: Feature) {
if (context.bridgeClient.getDebugProp("disable_feature_loading") == "true") {
context.log.warn("Feature loading is disabled")
@@ -61,7 +65,6 @@ class FeatureManager(
fun init() {
register(
Debug(),
- SecurityFeatures(),
EndToEndEncryption(),
ScopeSync(),
PreventMessageListAutoScroll(),
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/SecurityFeatures.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/SecurityFeatures.kt
@@ -1,85 +0,0 @@
-package me.rhunk.snapenhance.core.features.impl
-
-import android.annotation.SuppressLint
-import android.system.Os
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.NotInterested
-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.core.features.Feature
-import me.rhunk.snapenhance.core.util.ktx.setObjectField
-import java.io.FileDescriptor
-
-class SecurityFeatures : Feature("Security Features") {
- private fun transact(option: Int, option2: Long) = runCatching { Os.prctl(option, option2, 0, 0, 0) }.getOrNull()
-
- private val token by lazy {
- transact(0, 0)
- }
-
- private fun getStatus() = token?.run {
- transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
- }
-
- @SuppressLint("SetTextI18n")
- override fun init() {
- 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)?.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()
- }!!
-
- buffer = ProtoEditor(buffer).apply {
- edit(errorDataIndex) {
- remove(1)
- addString(1, status)
- }
- }.toByteArray()
- }
- }
- }
-
- val status = getStatus()
- val canCheckVersion = context.bridgeClient.getDebugProp("disable_mod_detection_version_check", "false") != "true"
- val snapchatVersionCode = context.androidContext.packageManager.getPackageInfo(context.androidContext.packageName, 0).longVersionCode
-
- if (canCheckVersion && MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED && (status == null || status < 2)) {
- onNextActivityCreate {
- 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
- )
- }
- }
- }
-}-
\ No newline at end of file