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:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt | 3+--
Acore/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 30+-----------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt | 5++++-
Dcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/SecurityFeatures.kt | 86-------------------------------------------------------------------------------
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